In Symfony, I must display a form that can have up to 800+ fields, all of them checkboxes, in order to tally attendance for a given month. I am well aware that, if I send the form and perform an update, I will have well over 800 update statements alone (not counting any possible inserts), and this is undesirable as it might perform poorly in terms of memory and time.
I know Doctrine can manage Lifecycle Callbacks, however I'm unexperienced on them and I believe the solution is here. How do I unmark an entity for merge if its value has not changed?
I came across this recently and it helped me with a similar situation. What you will want to use is doctrine entity managers transactional function. Something like:
//Controller Action
$form = $this->createForm(new YourFormType);
if($request->isMethod('post')){
$form->handleRequest($request);
if($form->isValid())
{
$data = $form->getNormData();
//If you have some other way of getting all your data use that
$em = $this->getDoctrine()->getManager();
$em->transactional(function($em) use($data){
foreach($data as $user){
$attendance = new Attendance(new \DateTime);
//Total assumption on how you handle attendance
$user->addAttendance($attendance);
$em->flush();
}
});
}
}
This will lump all of your updates into a single transaction and will also handle reverting if it fails.
For more info take a look at http://docs.doctrine-project.org/en/2.0.x/reference/transactions-and-concurrency.html
Related
I want to delete a file related to given entity after the entity itself is removed from the database. Doctrine's "postRemove" event seems perfect for that, except when the removal happens inside a transaction. If I rollback the transaction after removing the entity - the files will get deleted but the entity will remain in the database.
The project I'm working on is a very big one and this entity is widely used. Often it also gets removed through "cascade=remove", so the "you can just not use events and do it manually every time" soultion would be kind of a headache to implement.
An example case:
$img = new Image();
$img->setFileName('test.jpg');
$em->persist($img);
$em->flush();
$em->beginTransaction();
$em->remove($img);
$em->flush();
$em->rollback();
The event itself would be like:
public function postRemove(LifecycleEventArgs $args) {
$entity = $args->getObject();
$em = $args->getObjectManager();
if ($entity instanceof Image && file_exists($entity->getFileName())) {
unlink($entity->getFileName());
}
}
After executing that code the "test.jpg" file will be deleted, but the entity will still remain.
What I want to achieve is: on rollback both the file and the entity remain and on commit both of them are removed.
How could I make a transaction using multiple php controllers to make the queries?
Currently, my code is like this (A bit more complex, but I am not allowed to show anymore)
$operation = new Operations();
$bill = new Bills();
$employee = new Employee();
$operation->setName("Name");
$operationCreated = $operation->create();
$result = "";
if($operationCreated){
$bill->setAmount(1000);
$billCreated = $bill->create();
if($billCreated){
$employee->setName("Name");
$result= $employee->create();
}
}
return $result;
The way it is right now leads to a problem where I might have an operation created well, but no billing and no employee and my database ends with a row I don't want because I don't have all the information I need in the database.
I need a way to revert the changes if any one of them fails, and I think a transaction would do the trick, but I don't know how can be done. All three classes extend the Database class that has the create() method.
I have two entities: Projects and Task. I can implements this object as Value Object but I wonder about the whether that is good approach? Task might change own title or status and VO should be immutable. How implements this object?
I wonder about the in Project entity I should add addTask method or I should add Tasks via TaskController? Whether TaskController is necessary when Project entity has addTask method ?
Read this documentation on Doctrine Associations / Relations:
http://symfony.com/doc/current/doctrine/associations.html
It should explain what you need to do.
Essentially, your Project Entity should have an addTask() method where you add the task. Your Project will have an ArrayCollection of Tasks. Then you can use you getTask() method (you create this) to get the Task (if you need it).
The documentation gives good examples, so take a at that first.
EDIT #2 Based on comments.
So, it's seems you don't understand the article very well. You would have separate methods in each of your Entities to do what you need that is related to that particular Entity. I'm not certain what methods you actually want.
So for example, you gave in the comments two type of methods: changeTask and changeNameTask.
In you code, you could do something like this:
$project = new Project();
$task1 = new Task();
$task1->setName("My Task Name");
... // Do other things with task1
$project->addTask($task1);
$em = $this->getDoctrine()->getManager();
$em->persist($project); // Save to db.
$em->persist($task1);
$em->flush();
// Now let's add a new Task (different name).
$task2 = new Task();
$task2->setName("Another Task");
...
$project->addTask($task2);
// Remove the old Task...
$em->remove($task1);
$em->persist($project); // Save to db.
$em->persist($task2);
$em->flush();
// You can also get the Task if you need it.
$task2 = $project->getTask(); // Presumes that this is an object not an array.
The above should make sense...
We are trying to detect the changes in Laravel related models at attribute level, as we have to keep audit trail of all the changes which are made via the application.
We can track the changes via isDirty method on the Eloquent model for single model that is not related to any other model, but there is no way that we can track the changes on the related eloquent models. isDirty doesn't work on related models attributes. Can some one please help us on this?
Update to original question:
Actually we are trying to track changes on the pivot table that has extra attributes as well defined on it. IsDirty method doesn't work on those extra attributes which are defined in the pivot table.
Thanks
As much I understand your question, It's can achieve through Model Event and some sort of extra code with current and relation model.
Laravel Model Events
If you dont want to use any additional stuff, you can just use the Laravel Model Events (that in fact Ardent is wrapping in the hooks). Look into the docs http://laravel.com/docs/5.1/eloquent#events
Eloquent models fire several events, allowing you to hook into various
points in the model's lifecycle using the following methods: creating,
created, updating, updated, saving, saved, deleting, deleted,
restoring, restored.
Whenever a new item is saved for the first time, the creating and
created events will fire. If an item is not new and the save method is
called, the updating / updated events will fire. In both cases, the
saving / saved events will fire.
If false is returned from the creating, updating, saving, or deleting
events, the action will be cancelled:
Finally, reffering to your question you can utilize the above approaches in numerous ways but most obviously you can combine it (or not) with the Eloquent Models' getDirty() api docs here method and getRelation() api docs here method
It will work for example with the saving event.
Model::saving(function($model){
foreach($model->getDirty() as $attribute => $value){
$original= $model->getOriginal($attribute);
echo "Changed";
}
$relations = $model->getRelations();
foreach($relations as $relation){
$relation_model = getRelation($relation);
foreach($relation_model->getDirty() as $attribute => $value){
$original= $relation_model->getOriginal($attribute);
echo "Relation Changed";
}
}
return true; //if false the model wont save!
});
Another Thought might help you. when you saving
save() will check if something in the model has changed. If it hasn't it won't run a db query.
Here's the relevant part of code in Illuminate\Database\Eloquent\Model#performUpdate:
protected function performUpdate(Builder $query, array $options = [])
{
$dirty = $this->getDirty();
if (count($dirty) > 0)
{
// runs update query
}
return true;
}
The getDirty() method simply compares the current attributes with a copy saved in original when the model is created. This is done in the syncOriginal() method:
public function __construct(array $attributes = array())
{
$this->bootIfNotBooted();
$this->syncOriginal();
$this->fill($attributes);
}
public function syncOriginal()
{
$this->original = $this->attributes;
return $this;
}
check model is dirty isDirty():
if($user->isDirty()){
// changes have been made
}
Or check certain attribute:
if($user->isDirty('price')){
// price has changed
}
I did not check this code but hopeful to use as your answer by thoughts, if you have any confusion to deal such requirement or something need to optimize or change please let me know.
Is it possible to compare the state of an entity object between the current "dirty" version (an object that has some of its properties changed, not yet persisted) and the "original" version (the data still in the database).
My assumption was that I could have a "dirty" object, then pull a fresh one from the DB and compare the two. For instance:
$entity = $em->getRepository('MyContentBundle:DynamicContent')->find($id);
$editForm = $this->createContentForm($entity);
$editForm->bind($request);
if ($editForm->isValid()) {
$db_entity = $em->getRepository('MyContentBundle:DynamicContent')->find($id);
// compare $entity to $db_entity
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('content_edit', array('id' => $id)));
}
But in my experience, $entity and $db_entity are always the same object (and have the same data as $entity, after the form $request bind). Is there a way to get a fresh version of the $entity alongside the "dirty" version for comparison's sake? The solutions I've seen all pull the needed data before the form bind happens, but I'd rather not have that limitation.
Update: To clarify, I'm looking not only for changes to the entities' properties but also its related collections of entities.
You can get what has changed on the entity through Doctine's UnityOfWork. It is quite simple : after you persisted the entity, Doctrine knows what to update in the database. You can get these informations by doing :
// Run these AFTER persist and BEFORE flush
$uow = $em->getUnitOfWork();
$uow->computeChangeSets();
$changeset = $uow->getEntityChangeSet($entity);
After you flush the $em it happens (it is commited) in database.. so... you might want to retrieve the $db_entity before flush()
I am not sure what you want.. but you can also use merge instead of persist.
merge is returning the object modified - id generated and setted
persist is modifying your instance
If you want to have the object modified and not persisted use it before flush.
EntityManager is giving you the same instance because you didn't $em->clear()
flush is commiting all changes (all dirty objects)
clear is clearing memory cache. so when you find(..., $id) , you will get a brand new instance
Is clone keyword working for you? like in this example:
$entity = $em->find('My\Entity', $id);
$clonedEntity = clone $entity;
And you might also want to read this: Implementing Wakeup or Clone