This is what I do in even preUpdate
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof Order) {
if ($args->hasChangedField('status') && $args->getNewValue('status') == 'stock') {
$this->container->get('activity_logger')->writeLog($entity, 'purchase');
}
}
This is where I have error
FatalErrorException: Error: Maximum execution time of 60 seconds
exceeded in /vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php line 2498
public function writeLog ($object, $comment)
{
$entity = new Stock();
$entity->setCategory($object->getIsotope()->getCategory()->getId());
$entity->setComment($comment);
$entity->setDate(new \DateTime('now'));
$entity->setUser($object->getUser()->getId());
$entity->setChange(TRUE);
$this->em->persist($entity);
$this->em->flush();
}
There are store a new entity another way?
find not very nice solution(do it manually) its save and dont touch events
$sql = "INSERT INTO table (field1, field2) VALUES ('foo', 'var')";
$stmt = $em->getConnection()->prepare($sql);
$stmt->bindValue('invoice', $invoiceId);
$result = $stmt->execute();
I think that you can't do this because the output of a managed Entity contains recursive fields.
Take a look at https://github.com/schmittjoh/JMSSerializerBundle :) And log the serialized version ;)
To do that I store the newly created entities in a private array in the EventSuscriber. Then on the onFlush part of the suscriber if that array length is greater than 0 I persist them with a foreach loop. Then, outside the foreach loop, I clear the array (this is very important to prevent infinite loop) and finally call the flush.
Related
I have a listener for onFlush event.
public function onFlush(OnFlushEventArgs $args) {
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
$id = ...
}
}
I would like to get the Id of the $entity. If I call getId() then return null. Is there any way to finish the flushing inside this listener and get the $entity's Id?
Thank you advance
Accessing the ID before the SQL INSERT command is executed is impossible but you can access the MySQL table's auto increment value with some tricky ways like this:
$classMeta = $em->getClassMetadata($className);
if ($classMeta->rootEntityName !== $className) {
// if the Entity is inherited, only root entity table may have auto increment ID
$classMeta = $em->getClassMetadata($classMeta->rootEntityName);
}
$tableName = $classMeta->getTableName();
$conn = $em->getConnection();
$stmt = $conn->prepare("SHOW TABLE STATUS LIKE '".$tableName."'");
$stmt->execute();
$result = $stmt->fetch(\PDO::FETCH_ASSOC);
$id = $result['Auto_increment'];
Be aware using this:
this solution is hacky, auto increment value may differ from inserted ID
your MySQL user must have permission to run this SQL command
I need to invoke event when arraycollection is cleared (has no elements), pre/postUpdate is not invoked then. Only if I change contents of arraycollection and there is at least one element after change pre/postUpdate events are invoked.
Any Idea how to make it work?
public function postUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$em = $args->getEntityManager();
if ($entity instanceof Track) {
// remove all old statuses
$query = $em->createQuery(
'SELECT os
FROM CuculoERPBundle:OrderStatus os
WHERE os.track = :track'
)
->setParameter(':track', $entity->getId());
$orderStatusArray = $query->getResult();
foreach ($orderStatusArray as $orderStatus) {
$em->remove($orderStatus);
}
// add new statuses
foreach ($entity->getOrders() as $order) {
var_dump($order->getId());
$orderStatus = new OrderStatus();
$orderStatus->setOrder($order);
$orderStatus->setTrack($entity);
$orderStatus->setStep($em->getReference('Cuculo\ERPBundle\Entity\OrderStep', 6));
$em->persist($orderStatus);
}
$this->needsFlush = true;
}//end if
}//end postUpdate()
I managed to do this by copying code after // remove all old statuses to UpdateAction and adding conditional to count ArrayCollection and if 0 exec this code.
I am trying to do something when a User joins a Group. I am trying to use the preUpdate event for it and then check if the corresponding relations have changed. Unfortunately in my Group the 'users" relation is never in the changeset, as well as in my User the Usergroup is never in the changeset.
Here the two listeners:
public function preUpdate(PreUpdateEventArgs $args){
if($args->hasChangedField('users')){
$old = $args->getOldValue('users');
$new = $args->getNewValue('users');
}
}
public function preUpdate(PreUpdateEventArgs $args){
if($args->hasChangedField('userGroups')){
$old = $args->getOldValue('userGroups');
$new = $args->getNewValue('userGroups');
}
}
Thats my TestCase:
$group->addUser($user);
$em->beginTransaction();
$em->persist($group);
$em->flush();
$em->rollback();
Both listeners are called, but the relation is never in the changeset.
I want to add the user into a redis table, where I manage some specific data. Maybe the onFlush or some other events are better, since I don't need to modify the saved data. I just want to know if a there is a new Entry in my User-UserGroup Relation. I thought the easiest way to check this would be the changeset within the preUpdate function.
I solved it via the onFlush event:
public function onFlush(OnFlushEventArgs $args){
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledCollectionUpdates() as $col) {
if($this->isUserGroupUserAssociation($col->getMapping())){
$userGroup = $col->getOwner();
foreach($col->getInsertDiff() as $user){
$this->container->get('strego_user.user_manager')->triggerJoined($userGroup,$user);
}
}
}
}
protected function isUserGroupUserAssociation($association){
return($association['fieldName'] == "users" &&
$association['sourceEntity'] == "Strego\UserBundle\Entity\UserGroup"
);
}
I have managed to execute raw SQL (ie no ResultSetMapping) and can call and execute an MSSQL Stored Procedure.
The code I have is as follows:
$em = $this->get('doctrine')->getManager();
$stmt = $em
->getConnection()
->prepare('EXEC someSP :id,null,:uid');
$stmt->bindValue('id', '629674');
$stmt->bindValue('uid', '217');
$stmt->execute();
$results = $stmt->fetchAll();
No that works fine; however the issue i have is if the SP returns more than one result set the above only returns the first result set. Is there any way to loop through and get each result set?
I had to deal with the similar issue and this is what I came up with. This function is from the class that extends Doctrine\ORM\EntityRepository so I have access to the _em property in general case you can use $this->get('doctrine')->getManager(); to get entity manager.
protected function execMultiSetQuery($query, $params, $connection = 'default') {
// Init
$conn = $this->_em
->getConnection($connection)
->getWrappedConnection();
// Processing
if ($conn instanceof \Doctrine\DBAL\Driver\PDOConnection) {
$stmt = $conn->prepare($query);
$stmt->execute($params);
// Loop through the row sets
$results = array();
do {
try {
$results[] = $stmt->fetch(\PDO::FETCH_ASSOC);
}
catch (\Exception $e) {}
} while($stmt->nextRowset());
$stmt->closeCursor(); // Clean up
return $results;
}
else {
return false;
}
}
OK I was able to do this but i had to create my own wrapper around the connection:
Its a work around at best but the code basically creates a new PDO connection using the DBAL params in conf.yml. it them prepares and executes the statement and returns all the result sets.
code is free to use here:
https://github.com/scott-davidjones/Symfony2DBALSPWrapper
You can try and itterate over each set 1 by one. But really fetchAll should do the same as below, cant hurt to try though...
$em = $this->get('doctrine')->getManager();
$stmt = $em
->getConnection()
->prepare('EXEC someSP :id,null,:uid');
$stmt->bindValue('id', '629674');
$stmt->bindValue('uid', '217');
$stmt->execute();
do{
$results[] = $stmt->fetchAll()
} while($stmt->nextRowset());
I asked this question and found out that we can't get the error message thrown by a DataTransformer (according to the only user who answered, maybe it's possible, I don't know).
Anyway, now that I know that, I am stucked with a problem of validation. Suppose my model is this one: I have threads that contains several participants (users).
<?php
class Thread
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="My\UserBundle\Entity\User")
* #ORM\JoinTable(name="messaging_thread_user")
*/
private $participants;
// other fields, getters, setters, etc
}
For thread creation, I want the user to specify the participants usernames in a textarea, separated by "\n".
And I want that if one or more of the usernames specified don't exist, a message is displayed with the usernames that don't exist.
For example, "Users titi, tata and toto don't exist".
For that I created a DataTransformer that transforms the raw text in the textarea into an ArrayCollection containing instances of users. Since I can't get the error message provided by this DataTransformer (such a shame! Is it really impossible?), I don't check the existence of each usernames in the DataTransformer but in the Validator.
Here is the DataTransformer that converts \n-separated user list into an ArrayCollection (so that the DataBinding is ok):
<?php
public function reverseTransform($val)
{
if (empty($val)) {
return null;
}
$return = new ArrayCollection();
// Extract usernames in an array from the raw text
$val = str_replace("\r\n", "\n", trim($val));
$usernames = explode("\n", $val);
array_map('trim', $usernames);
foreach ($usernames as $username) {
$user = new User();
$user->setUsername($username);
if (!$return->contains($user)) {
$return->add($user);
}
}
return $return;
}
And here is my validator:
<?php
public function isValid($value, Constraint $constraint)
{
$repo = $this->em->getRepository('MyUserBundle:User');
$notValidUsernames = array();
foreach ($value as $user) {
$username = $user->getUsername();
if (!($user = $repo->findOneByUsername($username))) {
$notValidUsernames[] = $username;
}
}
if (count($notValidUsernames) == 0) {
return true;
}
// At least one username is not ok here
// Create the list of usernames separated by commas
$list = '';
$i = 1;
foreach ($notValidUsernames as $username) {
if ($i < count($notValidUsernames)) {
$list .= $username;
if ($i < count($notValidUsernames) - 1) {
$list .= ', ';
}
}
$i++;
}
$this->setMessage(
$this->translator->transChoice(
'form.error.participant_not_found',
count($notValidUsernames),
array(
'%usernames%' => $list,
'%last_username%' => end($notValidUsernames)
)
)
);
return false;
}
This current implementation looks ugly. I can see the error message well, but the users in the ArrayCollection returned by the DataTransformer are not synchronized with Doctrine.
I got two questions:
Is there any way that my validator could modify the value given in parameter? So that I can replace the simple User instances in the ArrayCollection returned by the DataTransformer into instances retrieved from the database?
Is there a simple and elegant way to do what I'm doing?
I guess the most simple way to do this is to be able to get the error message given by the DataTransformer. In the cookbook, they throw this exception: throw new TransformationFailedException(sprintf('An issue with number %s does not exist!', $val));, if I could put the list of non-existing usernames in the error message, it would be cool.
Thanks!
I am the one that answered your previous thread so maybe someone else will jump in here.
Your code can be simplified considerably. You are only dealing with user names. No need for use objects or array collections.
public function reverseTransform($val)
{
if (empty($val)) { return null; }
// Extract usernames in an array from the raw text
// $val = str_replace("\r\n", "\n", trim($val));
$usernames = explode("\n", $val);
array_map('trim', $usernames);
// No real need to check for dups here
return $usernames;
}
The validator:
public function isValid($userNames, Constraint $constraint)
{
$repo = $this->em->getRepository('SkepinUserBundle:User');
$notValidUsernames = array();
foreach ($userNames as $userName)
{
if (!($user = $repo->findOneByUsername($username)))
{
$notValidUsernames[$userName] = $userName; // Takes care of dups
}
}
if (count($notValidUsernames) == 0) {
return true;
}
// At least one username is not ok here
$invalidNames = implode(' ,',$notValidUsernames);
$this->setMessage(
$this->translator->transChoice(
'form.error.participant_not_found',
count($notValidUsernames),
array(
'%usernames%' => $invalidNames,
'%last_username%' => end($notValidUsernames)
)
)
);
return false;
}
=========================================================================
So at this point
We have used transformer to copy the data from the text area and generated an array of user names during form->bind().
We then used a validator to confirm that each user name actually exists in the database. If there are any that don't then we generate an error message and form->isValid() will fail.
So now we are back in the controller, we know we have a list of valid user names (possibly comma delimited or possibly just an array). Now we want to add these to our thread object.
One way would to create a thread manager service and add this functionality to it. So in the controller we might have:
$threadManager = $this->get('thread.manager');
$threadManager->addUsersToThread($thread,$users);
For the thread manager we would inject our entity manager. In the add users method we would get a reference to each of the users, verify that the thread does not already have a link to this user, call $thread->addUser() and then flush.
The fact that we have wrapped up this sort of functionality into a service class will make things easier to test as we can also make a command object and run this from the command line. it also gives us a nice spot to add additional thread related functionality. We might even consider injecting this manager into the user name validator and moving some of the isValid code to the manager.