Doctrine Events, update persisted entity - php

i wrote an Importer script, which read entries from an csv file,
and iterate the rows. To handle big files without performance loss,
i insert new data within doctrine batch(bulks).
My problem at the moment is, i have an "Category" entity, which should be expanded
only within new entries. So i have to check if entries are available given category names.
My first question is, i've read that doctrines "prePersist" event will be called on call
"$entityManager->persist()" and inside the "flush" method (http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#prepersist).
So how can i check if the event was inside an flush?
The next thing, how can i update the actually entity within the identity datas?
I try to set the id, but without any effect.
<?php
/**
* #return \Doctrine\Commong\Collections\ArrayCollection
*/
public function getCategories()
{
if (null === $this->categories) {
$this->categories = $this->getServiceCategory()->findAll();
}
return $this->categories;
}
public function prePersist(LifecycleEventArgs $event)
{
$entity = $event->getEntity();
$objectManager = $event->getObjectManager();
if ($entity instanceof \Application\Entity\Category) {
$categories = $this->getCategories();
$entityCriteria = // buildCriteria from Entity;
$matched = $categories->matching($entityCriteria);
if ($matched->count() > 0) {
$entity->setId($matched->first()->getId();
}
}
}
So, here i dont know how to update the persisted categorie entity?
Is this the right event, or should be an other event a better solution for my situation?
I developed the import within zf2 and doctrine2.
regards

First I would recommend to use DQL instead of ORM entities within you import script, because it makes you code much more simple.
Your import process increases the performance, if you first (1.) read all existing "Categories" from yor database, keep them within a member variable and second (2.) iterate each csv row and compare its category foreign key with the initially read set of categories.
If the category already exists in you database, create a new entity row with the existing corresponding foreign key, else create a new category and create a new entity row associated to the new category.
<?php
// read categories from database
$categories = "..."; /* e.g. array('A' => 1,
'B' => 2, ...); */
// ...
// Iterate each csv row
foreach($csvRows as $csvRow) {
// check category name
if(!array_key_exists($csvRow['category']), $categories) {
// Create new Category
// Remember id of the new created category
$categoryId = "...";
} else {
// Use already existing category id
$categoryId = $categories[$csvRow['category']];
}
// Create new csv row entity with category foreign key
// ...
}
?>

Related

Assign a relationship after loop in php

I want to loop over a collection of items and attach a relationship based on if a particular condition is satisfied. Here is my code
public function bulkAssign()
{
$trainers = MasterTrainer::all();
for ($i=0; $i < count($trainers); $i++) {
$this->assignToManager($trainers[$i]);
}
// return redirect()->back()->with('success', 'Project Managers Assigned Successfully');
}
private function assignToManager($trainer)
{
$manager = ProjectManager::where('state', $trainer->state)->first();
return $trainer->update([
'project_manager_id' => $manager->id
]);
}
What I get is it attaches only the first manager to all the elements in the collection. What am i doing wrong?
can you inline the func for now? do some sort of echo/debugging?
but also I see several issues:
yes do use foreach because that is a bit better and you avoid having to use $i (making code a little more easy to read)
you are not attaching a relationship, you are setting a project_manager_id (i say this because initially i automatically thought you were going to dynamically add a relationship to model)
without knowing your db schema.. could you not do some sort of trick to avoid having to do this nth times?
$manager = ProjectManager::where('state', $trainer->state)->first();
you could either do:
$states = $trainers->pluck('states');
$managers = // do a query to get one trainer per state using group by
foreach ($trainers... ) {
$manager = $managers->where('state', $trainers->state)->first() // this is collection not eloquent
$trainer->update([
'project_manager_id' => $manager->id
]);
other would be to create a scope where you do a sub query to get manager id when u query for trainers

How can I clone all data from my database with Symfony doctrine?

I try to clone all records in my data entity that have the item value cf7c1ae00f
$dataEntity= new Data();
$cloneArray = $this->em->getRepository(Data::class)->findBy(['item' => 'cf7c1ae00f']);
foreach ($cloneArray as $cloneItem) {
$fieldClone = clone $cloneItem;
$dataEntity->setItem($fieldClone);
$this->em->persist($dataEntity);
}
$this->em->flush();
In my database there are 5 records. So I expect that another 5 records are added. But only one record is added.
You are writing the same $dataEntity 5 times with different contents. You could construct that object in the loop to solve your problem but you could also persist $fieldClone directly instead and skip the $dataEntity variable completely.
However, entities have unique ids and that will lead to errors when you try to persist a cloned entity. You would have to empty the id and other attributes that have to be unique in the collection / database.
You can easily set some initial values on a new object when the clone keyword is used, using the __clone() method of the class that the object belongs to.
So if you only need to empty the id, you would add a clone method to the Data class and change the loop to:
Data class:
public function __clone() {
$this->id = null;
}
Cloning code:
$cloneArray = $this->em->getRepository(Data::class)->findBy(['item' => 'cf7c1ae00f']);
foreach ($cloneArray as $cloneItem) {
# Clone the object and automatically run the `__clone()` method of the class
$fieldClone = clone $cloneItem;
$this->em->persist($fieldClone);
}
$this->em->flush();

Update row with data from another row in the same table in Laravel

I am trying to update one line with another in my database. I can not find a way to do this.
I have a parent line and a child line. I want to update the PARENT activity with the CHILD activity (draft).
In my controller :
public function update(UpdateRequest $request)
{
if (Auth::user()->hasRole('Administrateur'))
{
// DRAFT ACTIVITY
$id = $request->id;
$activity = Activity::find($id);
// PARENT ACTIVITY
$parent_id = $activity->parent_id;
$parent_activity = Activity::find($parent_id);
DB::table('activities')->???
}
}
Can you help me ? Thank you very much !
The easiest way would be to use the update method on your Activity model and pass it the new data as parameter.
Eg:
$parent_activity->update([
'someProperty' => $activity->someProperty,
...
]);
Don't forget to enable mass assignment by adding protected property, $fillable, to your Activity model.
To copy all data to your $parent_activity you could do something like:
$someData = $activity->toArray();
$parent_activity->update($someData);
Be cautioned you update only the desired properties and not lets say the parent_id. You can prevent this by using the array_except() helper.
$someData = array_except($activity->toArray(), ['parent_id']);
$parent_activity->update($someData);

Symfony2/3: Is flushing a single entity/object possible? If not, how can I access objects persisted but not flushed after a foreach loop?

I know that in Doctrine (as a general rule) it is better to flush() after persisting all the entities/objects to the database, but in the following case I think it could be useful to do the opposite.
Example:
Imagine that you are cycling through a list of sport results like this one:
playerA_unique_tag (string), playerB_unique_tag (string), result
In the database, playerA and playerB are FOREIGN KEYS (that point to a User entity). So, the database structure would be similar to this one:
Match record
id, playerA_fk, playerB_fk, result
User records
id, playerA_unique_tag, (etc... many other fields)
id, playerB_unique_tag, (etc... many other fields)
Example of a script
$sportResultsArray = array();
foreach($sportResultsArray as $sportResult){
$playerA_tag = $sportResult["$playerA_unique_tag"];
$db_playerA = db->getRepository("App:User")->findOneByTag($playerA);
if(!$db_playerA){
$db_playerA = new User();
$db_playerA ->setPlayer_unique_tag($playerA_tag);
$em->persist($db_playerA );
}
$match = new Match();
$match ->setplayerA($db_playerA );
/*Same thing would be done for playerB*/
$em->persist($match );
}
Problem:
Of course playerA will play MULTIPLE matches, and each time I have to somehow retrieve the corresponding User object and pass it to the new Match object.
But how can I do that if I haven't flushed playerA User object yet.
The only two alternatives I can think of are:
1- Flushing the User entity (and ONLY the User entity) after it is created
2- Create a temporary array of objects like this:
array('playerA_unique_tag' => playerA_Object, etc.)
Problem with option_1:
I have tried $em->flush($db_playerA); but every entity that was persisted to the Entity Manager also gets flushed (contrary to what written here: http://www.doctrine-project.org/api/orm/2.5/source-class-Doctrine.ORM.EntityManager.html#338-359). Basically, the result is the same as $em->flush();
Problem with option_2:
Isn't it a bad and inefficient workaround?
Consider to work with in-memory registry of players as following:
// init registry
$players = [];
foreach ($sportResultsArray as $sportResult) {
$players[$sportResult["$playerA_unique_tag"]] = null;
$players[$sportResult["$playerB_unique_tag"]] = null;
}
// fetch all at once
$existing = $db->getRepository("App:User")->findBy(['tag' => array_keys($players)]);
// fill up the registry
foreach ($existing as $player) {
$players[$player->getTag()] = $player;
}
// match them up
foreach ($sportResultsArray as $sportResult) {
$playerA_tag = $sportResult["$playerA_unique_tag"];
if ($players[$playerA_tag] === null) {
$players[$playerA_tag] = new User();
$players[$playerA_tag]->setPlayer_unique_tag($playerA_tag);
$em->persist($players[$playerA_tag]);
}
$match = new Match();
$match->setplayerA($players[$playerA_tag]);
/*Same thing would be done for playerB*/
$em->persist($match);
}
// finally
$em->flush();

Skip Entities while flushing when they are a Duplicate

i'm playing a little bit with Symfony2 and Doctrine2.
I have an Entity that has a unique title for example:
class listItem
{
/**
* #orm:Id
* #orm:Column(type="integer")
* #orm:GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #orm:Column(type="string", length="255", unique="true")
* #assert:NotBlank()
*/
protected $title;
now i'm fetching a json and updating my database with those items:
$em = $this->get('doctrine.orm.entity_manager');
foreach($json->value->items as $item) {
$listItem = new ListItem();
$listItem->setTitle($item->title);
$em->persist($listItem);
}
$em->flush();
works fine the first time. but the second time i'm getting an sql error (of course): Integrity constraint violation: 1062 Duplicate entry
sometimes my json file gets updated and some of the items are new, some are not.
Is there a way to tell the entity manager to skip the duplicate files and just insert the new ones?
Whats the best way to do this?
Thanks for all help. Please leave a comment if something is unclear
Edit:
what works for me is doing something like this:
$uniqueness = $em->getRepository('ListItem')->checkUniqueness($item->title);
if(false == $uniqueness) {
continue;
}
$listItem = new ListItem();
$listItem->setTitle($item->title);
$em->persist($listItem);
$em->flush();
}
checkUniqueness is a method in my ListItem Repo that checks if the title is already in my db.
thats horrible. this are 2 database queries for each item. this ends up about 85 database queries for this action.
How about retrieving all the current titles into an array first and checking the inserting title against the current titles in that array
$existingTitles = $em->getRepository('ListItem')->getCurrentTitles();
foreach($json->value->items as $item) {
if (!in_array($item->title, $existingTitles)) {
$listItem = new ListItem();
$listItem->setTitle($item->title);
$em->persist($listItem);
}
}
$em->flush();
getCurrentTitles() would need to be added to ListItem Repo to simply return an array of titles.
This only requires one extra DB query but does cost you more in memory to hold the current titles in an array. There maybe problems with this method if your dataset for ListItem is very big.
If the number of items your want to insert each time isn't too large, you could modify the getCurrentTitles() function to query for all those items with the titles your trying to insert. This way the max amount of $existingTiles you will return will be the size of your insert data list. Then you could perform your checks as above.
// getCurrentTitles() - $newTitles is array of all new titles you want to insert
return $qb->select('title')
->from('Table', 't')
->in('t.title = ', $newTitles)
->getArrayResult();
If you are using an entity that may already exists in the manager you have to merge it.
Here is what I would do (did not test it yet) :
$repository = $this->get('doctrine.orm.entity_manager');
foreach($json->value->items as $item) {
$listItem = new ListItem();
$listItem->setTitle($item->title);
$em->merge($listItem); // return a managed entity
// no need to persist as long as the entity is now managed
}
$em->flush();

Categories