Automated count in doctrine 2 - php

Using Doctrine 2 and Symfony 2.7 I want to use an automated count for a column in my db.
Example:
So when I update a report, I want to add the user (which is the parent of the report by a OneToMany relation) to the leaderboards with the column completed set to 1 (setCompleted). When the user was already on the leaderboards, I want to find him and add 1 to the completed tasks value.
if (!$lb) {
$new = New Leaderboard();
$new->setUsers($user)
->setCompleted('1');
$em->persist($new);
} else {
$update = $em->getRepository('AppBundle:Leaderboard')->findBy(array('user' => $user));
$update->setCompleted('2');
}
So basically I want to automate the $update->setCompleted('2'); so that it takes the current value and adds one to that and then flush that to the database.
I hope this makes any sense? No sure how to explain it or search for it online...

You could use an onFlush listener to watch your Report entity for updates and increment/create the leaderboard entry depending on the state of the Report.
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/events.html#onflush
Requires a bit of (careful) reading, but it will do what you want.
Here's a couple of questions I answered on more or less the same subject with examples that should get you started pretty quick
Track field changes on Doctrine entity
Persisting other entities inside preUpdate of Doctrine Entity Listener
The basic flow you want is to capture updates to Report (the simpler second example under my answer shows this being done for an entity). Then based on the state of Report, you create a new Leaderboard entity and attach it to Report, OR update the existing Leaderboard entity for that report.
Note it's important to not flush the entity, just add it to the UOW like so
$this->getEntityManager()->persist($entity);
$metaData = $this->getEntityManager()->getClassMetadata($className);
$this->getUnitOfWork()->computeChangeSet($metaData, $entity);
Hope that helps!

Related

Can't get my old Entity in preUpdate Sonata

Here's the deal, i'm trying to update an Entity, but i'd like to compare the state of my entity before the update with the state of my entity after the update.
I try to compare the number of Users in my entity.
For example, if i add a User, i want my code to know that, for example, i had 4 Users before update, and 5 after (and get that User for further use).
After reading different topics, i've tried doing like that in my preUpdate($object){} method
$em = this->getConfigurationPool()->getContainer()->get('doctrine')->getManager();
$original = $em->getUnitOfWork()->getOriginalEntityData($object);
But both
var_dump(count($object->getUsers()));
var_dump(count($original['users']));
Give the same value, and, according to my example, the value is 5 in both case (so the value after the update).
Is there a way i can save the old_state of my entity in a var? What am i doing wrong?
EDIT:
It's not the preUpdate function of Doctrine, but the preUpdate function of SonataAdmin, don't know if they're the same.
Please try this in the preUpdate function:
$em = $this->getModelManager()->getEntityManager($this->getClass());
$original = $em->getUnitOfWork()->getOriginalEntityData($object);

Insert model unless it exists and attach it

I'm a Laravel noob rewriting some old code to Laravel.
I have a system for managing purchases and games and I'm writing the store method of the PurchaseController. The form for creating new purchases contains data about the purchase and an array with data about the games.
There is a many-to-many relationship between games and purchases: a purchase can contain many games and a game may be linked to multiple purchases.
The thing is that the game may already exist in the database. I want to do the following:
Insert the new purchase into the database (this part I got sorted out already ;))
Check if the POSTed name of the game already exists in the database.
If it exists, attach it to the newly inserted purchase. If it doesn't exist, insert it and attach it to the newly inserted purchase.
I don't want to update the game if it already exists in the database, just to attach it to the purchase.
I've looked into firstOrCreate but that doesn't do what I want. It checks on all the arguments you feed it, you can't just make it check only the name (this issue basically).
The undocumented method updateOrCreate does accept two arrays (one for attributes to check on, another for values to insert) but it updates the record if it exists, which is not what I want.
So, is there a nice, proper way to do this with Eloquent or do I simply need to manually write some code that checks if the game exists in the database and inserts the game unless that's the case?
EDIT:
It seems that this is possible with firstOrCreate after all in Laravel 5.3: https://github.com/laravel/framework/pull/13236
firstOrCreate is what you need, but you can feed it just the game name, then attach it to your purchase.
$game = Game::firstOrCreate(['name' => $gameName]);
$purchase = new Purchase(['otherArgs' => ...]);
$purchase->games()->attach($game);
I was probably overthinking this too much. The following code does what I want:
// Insert games (unless they exist) and attach to new purchase
foreach($request->games as $game) {
$gameModel = Game::firstOrNew(['name' => $game['name']]);
if(!$gameModel->exists) {
$gameModel->status_id = $game['status'];
$gameModel->note = $game['note'];
$gameModel->save();
}
$gameModel->purchases()->attach($purchase->id);
}
I just thought maybe there was a nicer/shorter way to do this.

Create something like SQL Trigger in Doctrine / Symfony 3

I have a little problem and I can't solve it.
I have a controller, which accept 4 variables using AJAX and I need to insert data in first table and then get ID value from first table and insert it to 2 with 2 additional parameters.
So, my app structure is:
1) Keywords Table with fields keywordId, KeywordVal and Page ID (getted from AJAX)
2) Translations table with fields keywordID (get from Keywords.keywordId), langCode and translation (AJAX)
3) Controller which get data from ajax, proceed it and insert into table.
So, my question is next: how can I configure my EventListener? This listener must be run after flush() method and insret data into Translations table.
Why not you create it all in the controller, because to do all in a EventListener you must in some way to pass the values to the listener.
If you do it in your controller yo can to persist first the first entity with the needed parameters and in after persist the second entity related to de previusly created entity.
Something like:
$em = $this->getDoctrine()->getManager();
$keyword = new Keyword($param1, $param2);
$em->persist($keyword);
$keywordTranslation = new KeywordTranslation($keyword, $param3, $param4);
$em->persist($keywordTranslation);
$em->flush();
I think is so much easy to do
What you seem to need is a Doctrine Listener. Here is the documentation to create a Doctrine listener. You might want to use the preFlush event in my opinion. Be careful, this event is fired for every flush, not only for Keywords, so you've got to check first it is a Keyword before creating Translations.
EDIT: Nevermind, just noticed this does not answer to your question. However, I feel your model could be improved, because you should not have to flush several times to insert a single set of data. Theoretically, you should have a OneToMany relationship between Keywords and Translations, and Doctrine would manage alone to link the two entities with their id at insertion.

Doctrine isDeletable method in entity

I have a Entity in my database (say Member) which has many relationships with other tables (6 relationships to be exact). Some of them I don't want mapped with the ORM (I mean linked to this Entity) because they may have many records (like MemberAccessLogs for example) and some other load many other entities.
Now I want this Member Entity to have an isDeletable method so I can disable exclude button in administration page.
If I where to do this the traditional way, I would have to declare the associations with all the other tables in the entity class, including MemberAccessLogs and I would put the method in it so I could test if these associations are empty.
But AFAIU, I would have to make a fetch (or at least a count) to the association's tables in order check for empty.
Another way would be to fetch the Members I want shown and then make a separate query to check for empty with a low cost exists(select * from table limit 1) in these sub-tables and then populate the isDeletable method in Member programmatically before pass it to Twig.
But I found this solution cumbersome. Anyone has a better way to do this ?
Just for the record: Some people may think this is "premature optimization". I maintain (contrary to some), that you should think ahead when you are programming and don't this this is bad. But I really think this isn't the place to discuss it. Please let's focus on the question asked ok ? :)
Edit
To easily prove that limit 1 is increadibly faster than count, I did a small test in a table in my database that has more than 20 million lines. Here are the results:
select count(*) from loga [20 million+ table]
20678473
1 row(s) fetched - 27023ms
select exists(select null from loga limit 1)
true
1 row(s) fetched - 2ms
I guess 13511,5 times faster is conclusive enough. :D
Extra lazy
You could look into extra-lazy associations.
Basically you map all associations as you normally would, and add fetch="EXTRA_LAZY":
/**
* #Entity
*/
class CmsGroup
{
/**
* #ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
*/
public $users;
}
Now Doctrine will not load the complete collection into memory the first time it's accessed, but performs specialized queries to load the parts you actually need at that moment.
So $users->count() (or count($users)) on the collection would trigger a simple count-query in stead of loading the complete collection into memory.
PostLoad
You could use an postLoad event to determine if such an entity is deletable. This postLoad event is called after an entity is constructed by the EntityManager, so when the entity is loaded.
Add an unmapped property ($isDeletable) to the entity that stores whether the entity can be deleted or not.
Create an entity listener that listens to the postLoad event. The listener can have the EntityManager, DBAL Connection, or anything else injected. With that dependency you could perform whatever query you want and use the result to set $isDeletable.
The result is a single additional query when the entity is loaded, after which the entity "knows" whether it's deletable or not.
An example of using the postLoad event can found in a Cookbook entry on the Strategy Pattern
Do note that when the conditions that determine whether it's deletable or not change, the value of $isDeletable could become incorrect. To resolve this issue, you could keep track of those conditions:
Keep track
Add a mapped property ($isDeletable) to the entity that stores whether the entity can be deleted or not. It would probably start with true.
When something is added to an association which would mean that the entity is no longer deletable, set $isDeletable to false.
When something is removed from an association that which would mean that the entity is deletable again, set $isDeletable to true.
In other words: with every change you keep track of whether the entity is deletable or not.
This way you won't need any additional queries at all.
There's a Cookbook entry on aggregate fields that explains this concept very well.

Transforming data collected from form before persisting to entity

I am a newbie at Symfony2 and just hacking my way through some existing codebase. I need help in understanding how to achieve the following outcome.
I have a PriceList entity that stores:
DollarCostPrice, PercentDiscount1, PercentDiscount2, PercentCommission
I want the user to enter following 4 values through a form:
SalePrice, DollarDiscount1Price, DollarDiscount2Price, PercentCommission
where,
SalePrice = (DollarCostPrice + (PercentCommission * DollarCostPrice))
DollarDiscount1Price = ((DollarCostPrice + (PercentCommission * DollarCostPrice)) * (100 - PercentDiscount1)/100)
DollarDiscount2Price = ((DollarCostPrice + (PercentCommission * DollarCostPrice)) * (1 - PercentDiscount2)/100)
But once user has entered the above values, I will compute DollarCostPrice, PercentDiscount1, PercentDiscount2, PercentCommission that need to be persisted in the entity.
I want to use a collection of above 4 fields in a form so that users can enter this information for multiple items at once.
I am still new with using forms, collections and data-transformers in Symfony2. I would appreciate if someone could help me determine the best way to do it.
I would do it this way. Note I'm making assumptions here, you haven't provided any information on what entities relate to PriceList for example.
Create a PriceListType form type, you would have 1 mapped field (PercentCommission) and 3 non-mapped fields (SalePrice, DollarDiscount1Price, DollarDiscount2Price)
To add a non-mapped field:
->add('SalePrice', null, array('mapped' => false))
I'm assuming that PriceList has a relation to some sort of SKU object or similar. You need to consider how you manage this relation.
Start with a basic form for your PriceList Type. I'd use something similar to this technique for adding new PriceList items.
On Save I would initially just do the entity calculations in the controller to get things up and running quickly E.g.
$yourForm->handleRequest($request);
if ($yourForm->isValid()) {
// loop through elements in your collection - note for non mapped fields
// you need to access them like so: $form->get("DollarDiscount1Price")->getData();
// calculate the data you wish to save to each PriceList entity, set and persist it
$entityManager->persist($priceListEntity);
// finish & finally flush
$entityManager->flush();
}
Then later I might move that functionality to a form event listener.
I don't see a need for a data transformer here myself as it'll add complexity. If you're planning to re-use this functionality in a number of forms maybe, but otherwise I don't think I'd worry about it.
People will tell you X is the ideal solution, but IMO start simple and refine as you go.

Categories