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...
Related
I'm new to Laravel, and trying to save the Car with a specific Wheels as follows;
$wheel1=new Wheel();
$wheel1->save();
$wheel2=new Wheel();
$wheel2->save();
$car= new Car();
$car->name='Mazda';
$car->wheelid_1='1';
$car->wheelid_2='2';
$car->save();
The problem I'm having is, I need to save the car, wheel1, and wheel2 objects at the same time without referring to or knowing their id's. But I have no wheelid_1 and wheelid_2 until I save the wheel1 and wheel2 first.
I referred this, this and other similar questions but was unable to figure out how to assign wheel1 and wheel2 as new related objects to the car model.
I have done similar tasks using Entity Framework, by just assigning child objects to relevant properties of the parent object with C#. Can I use a similar method in Eloquent?
I have added foreign keys for Wheels and Car on the table creation.
How can I make the reference between all these 03 objects without using their ids for saving?
What I can imagine is something like the below;
$wheel1=new Wheel();
$wheel2=new Wheel();
$car= new Car();
$car->name='Mazda';
$car->wheel1=$wheel1;
$car->wheel2=$wheel2;
$car->save();
Any help would be highly appreciated.
When you create a new model of item, in this case wheel. It gives you an id.
$wheel1=new Wheel();
$wheel1->save();
$wheel2=new Wheel();
$wheel2->save();
So you can use the model like this and retrieve the id
$wheel1->id;
$wheel2->id;
$car= new Car();
$car->name='Mazda';
if new car has property wheelid_1 & wheelid_2
you can save the ids of this wheel like so
$car->wheelid_1 = $wheel1->id;
$car->wheelid_2 = $wheel2->id;
$car->save();
A few bewares is that if you create a wheel model like that, make sure that in your migration the columns are set to nullable for no errors.
imagine I have some doctrine Entity and I can have some records of this entity in the database which I dont want to be deleted, but I want them to be visible.
In general I can have entities, for which I have default records, which must stay there - must not be deleted, but must be visible.
Or for example, I want to have special User account only for CRON operations. I want this account to be visible in list of users, but it must not be deleted - obviously.
I was searching and best what I get was SoftDeletable https://github.com/Atlantic18/DoctrineExtensions/blob/v2.4.x/doc/softdeleteable.md It prevents fyzical/real deletion from DB, but also makes it unvisible on the Front of the app. It is good approach - make a column in the Entity's respective table column - 1/0 flag - which will mark what can not be deleted. I would also like it this way because it can be used as a Trait in multiple Entities. I think this would be good candidate for another extension in the above Atlantic18/DoctrineExtensions extension. If you think this is good idea (Doctrine filter) what is the best steps to do it?
The question is, is this the only way? Do you have a better solution? What is common way to solve this?
EDIT:
1. So, we know, that we need additional column in a database - it is easy to make a trait for it to make it reusable
But
2. To not have any additional code in each repository, how to accomplish the logic of "if column is tru, prevent delete" with help of Annotation? Like it is in SoftDeletable example above.
Thank you in advance.
You could do this down at the database level. Just create a table called for example protected_users with foreign key to users and set the key to ON DELETE RESTRICT. Create a record in this table for every user you don't want to delete. That way any attempt to delete the record will fail both in Doctrine as well as on db level (on any manual intervention in db). No edit to users entity itself is needed and it's protected even without Doctrine. Of course, you can make an entity for that protected_users table.
You can also create a method on User entity like isProtected() which will just check if related ProtectedUser entity exists.
You should have a look at the doctrine events with Symfony:
Step1: I create a ProtectedInterface interface with one method:
public function isDeletable(): boolean
Step2: I create a ProtectionTrait trait which create a new property. This isDeletable property is annotated with #ORM/Column. The trait implements the isDeletable(). It only is a getter.
If my entity could have some undeletable data, I update the class. My class will now implement my DeleteProtectedInterface and use my ProtectionTrait.
Step3: I create an exception which will be thrown each time someone try to delete an undeletable entity.
Step4: Here is the tips: I create a listener like the softdeletable. In this listener, I add a condition test when my entity implements the ProtectedInterface, I call the getter isDeleteable():
final class ProtectedDeletableSubscriber implements EventSubscriber
{
public function onFlush(OnFlushEventArgs $onFlushEventArgs): void
{
$entityManager = $onFlushEventArgs->getEntityManager();
$unitOfWork = $entityManager->getUnitOfWork();
foreach ($unitOfWork->getScheduledEntityDeletions() as $entity) {
if ($entity instanceof ProtectedInterface && !$entity->isDeletable()) {
throw new EntityNotDeletableException();
}
}
}
}
I think that this code could be optimized, because it is called each time I delete an entity. On my application, users don't delete a lot of data. If you use the SoftDeletable component, you should replace it by a mix between this one and the original one to avoid a lot of test. As example, you could do this:
final class ProtectedSoftDeletableSubscriber implements EventSubscriber
{
public function onFlush(OnFlushEventArgs $onFlushEventArgs): void
{
$entityManager = $onFlushEventArgs->getEntityManager();
$unitOfWork = $entityManager->getUnitOfWork();
foreach ($unitOfWork->getScheduledEntityDeletions() as $entity) {
if ($entity instanceof ProtectedInterface && !$entity->isDeletable()) {
throw new EntityNotDeletableException();
}
if (!$entity instance SoftDeletableInterface) {
return
}
//paste the code of the softdeletable subscriber
}
}
}
Well the best way to achieve this is to have one more column in the database for example boolean canBeDeleted and set it to true if the record must not be deleted. Then in the delete method in your repository you can check if the record that is passed to be deleted can be deleted and throw exception or handle the situation by other way. You can add this field to a trait and add it to any entity with just one line.
Soft delete is when you want to mark a record as deleted but you want it to stay in the database.
I am trying to save an Entity that may or may not already exist and may or may not have one or two relations (code below). My current method results in errors, I am pretty sure I could come up with some workarounds/hacks on my own, but I am interested in the proper, "official" way to do it.
What I am trying now:
$entity = new myEntity();
if ( !empty($id) )
{
$entity->setId($id);
}
$entity->setLocationId($relation_id); //may or may not be null, if not null it's always an already existing location entry in a different table, i.e. not a new
$entity = $entity_manager->merge($entity);
$entity_manager->flush();
Currently Doctrine complains about Location being a new Entity with no ID and the policy does not allow generating IDs automatically. Policy is indeed such, but I do not add Location entity at all, I use setLocationId() auto generated method which adds precisely existing Location's ID so I am a bit puzzled.
edit: I get the doctrine error when location id is not null but a real, existing (in db) location's id.
And the model:
Location:
type: entity
table: locationstable
id:
my-id-column:
type: string
fields:
some fields
oneToMany:
myEntities:
targetEntity: myEntity
mappedBy: location
cascade: ["persist", "merge"]
If you are going to use Doctrine 2's ORM then you really need to use it as designed. That means working with objects and not id's. If you are not comfortable with this approach then switch to some sort of active record implementation.
//For your case, start by seeing if there is an existing entity:
// This eliminates all the merge nonsense
$entity = $entityManager->getRepository('EntityClassName')->find($entityId);
if (!$entity)
{
$entity = new Entity();
/* Only do this is you are not autogenerating id's
* However, it does raise the question of how you know what the id should be if the entity
* if the entity does not yet exist?
*/
$entity->setId($id);
// Persist the new entity
$entityManager->persist($entity);
}
// Now get a reference to the location object which saves loading the entire location
// which optimizes things a tiny (probably unnoticeable) bit
$location = $entityManager->getReference('LocationClassName',$locationId);
$entity->setLocation($location);
// And flush changes
$entityManager->flush();
And again, if you feel this is too complex or uses too many queries then don't use Doctrine 2. You will be fighting it constantly. In practice, Doctrine 2 turns out to perform quite well. No real need to worry about micro-optimizations.
Try to find the location record and add to the entity:
$entity = new myEntity();
if ( !empty($id) )
{
$entity->setId($id);
}
//$entity->setLocationId($relation_id); //may or may not be null, if not null it's always an already existing
$location = $locationRepository->find($relation_id);
$entity->setLocation($location);
location entry in a different table, i.e. not a new
$entity = $entity_manager->merge($entity);
$entity_manager->flush();
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
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