Doctrine Entity increase value (Download counter) - php

I want to increase a value in my doctrine entity.
Currently I'm doing it this way.
$file->setDownloadCounter($file->getDownloadCounter() + 1);
$em = $this->getDoctrine()->getManager();
$em->persist($fileVersion);
$em->flush();
Is there way to execute something like this in doctrine:
UPDATE file SET downloadCounter = downloadCounter + 1 WHERE id = 1
EDIT:
The problem in the doctrine example above is that between loading and flush is time where others could download the file and so the counter is not correct.

You can also do the following in an entity repository:
return $this
->createQueryBuilder('f')
->update($this->getEntityName(), 'f')
->set('f.downloadCounter', $file->getDownloadCounter() + 1)
->where('f.id = :id')->setParameter('id', $file->getId())
->getQuery()
->execute();
Or using DQL:
$em = $this->getDoctrine()->getManager();
$query = $em->createQuery(
'UPDATE YourBundle:File f
SET f.downloadCounter = :downloadCounter'
)->setParameter('downloadCounter', $file->getDownloadCounter() + 1);
Or through a simplified DQL:
$em = $this->getDoctrine()->getManager();
$query = $em->createQuery(
'UPDATE YourBundle:File f
SET f.downloadCounter = f.downloadCounter + 1'
);
The drawback with these solutions: if your entity was already loaded it will have the previous count and not the incremented count.
The way you did is perfectly fine but a better way is to add an increment method to your entity.
Follow-up from Radu C comment below: the simplified DQL is the only solution that guarantees proper count.
The query increments based on the value in the database and locks the table guaranteeing queries to be executed in a sequence.
Whereas the other queries use the value in PHP runtime which may be an outdated value: some other request may have already incremented the value in the database therefore incrementing based on value in PHP memory will override increment made by other requests.

Safest way to do this is using Doctrine DBAL and call raw SQL that way you remove the chance of race condition and make the change atomic. Other option is to make the field versioned and use optimistic locking or pessimistic DB-level locking.

You could simply add an increment method to your model.
class File {
public function increaseDownloadCounter()
{
$this->downloadCounter++;
}
}

$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('AcmeDemoBundle:EntityName')->find($entityId);
$valueToIncrement = $entity->getMyField(); // where MyField is the DB field to increment
$entity->setMyField(++$valueToIncrement);
$em->persist($entity);
$em->flush();

Related

Equal to DQL query

I have the following query that should check the start and end date in another entity and compare it against the start and end dates enter to create an instance of an entity, however its not returning anything.
public function createAction(Request $request)
{
$entity = new Payrollperiod();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$qb = $em->getRepository('comtwclagripayrollBundle:PayrollWeek')->createQueryBuilder('p');
$qb->select('p')
->where('p.startDate = :entityStart')
->andWhere('p.endDate = :entityEnd')
->setParameter('entityStart',$entity->getstartDate())
->setParameter('entityEnd',$entity->getendDate())
->getQuery()
->getResult();
How long I've been using Doctrine, I've never seen:
In MySQL (and PostgreSQL, SQLite) equal symbol is =, not ==.
In DQL you can't use entities' methods like getStartDate(), only attributes (in detail you can use only fields and associations defined in entity's mapping).
So:
$qb->select('p')
->where('p.getstartDate()==entity.startDate')
->andWhere('p.getendDate()==entity.endDate');
Should be:
$qb->select('p')
->where('p.startDate = entity.startDate')
->andWhere('p.getEndDate = entity.endDate')
What is entity.*? You have not declared entity Entity in your DQL.
[EDIT] If entity.* is another entity and you not define its in Query, then you must parameterize it:
$qb->select('p')
->where('p.startDate = :entityStart')
->andWhere('p.getEndDate = :entityEnd')
->setParameter('entityStart', $entity->getStartDate())
->setParameter('entityEnd', $entity->getEndDate())
Without exception message I cannot tell more about your code. It could be NonUniqueResultException because query finds more than 1 result.
Doctrine (and Symfony) have so many exceptions where everything is nice explain - what's crashed, why, where. Sometimes even the name of exception tell us everything - like UniqueConstraintViolationException.

Why isn't count returning the correct number of elements in a Doctrine Collection?

I have the following code:
$em = $this->getDoctrine()->getManager();
if($groupType == 'existing'){
$urlGroup = $em->getRepository('UrlBuilderBundle:UrlGroup')->find($groupId);
}elseif($groupType == 'new'){
$urlGroup = new UrlGroup();
$groupName = $submittedData['groupName'];
$urlGroup->setName($groupName);
$em->persist($urlGroup);
}
$url = new Url();
$url->setName($name);
$url->setAuthorUser($authorUser);
$url->setUrl($generatedUrl);
$url->setUrlGroup($urlGroup);
$em->persist($url);
$em->flush();
$urlGroupName = $urlGroup->getName();
$urlCount = count($urlGroup->getUrls());
When a new UrlGroup is created, the last line (count of child URL objects) always returns zero even when a URL has been added for the given UrlGroup. This code is used in an AJAX call.
Upon page refresh count() returns the correct number.
Appreciate it if anyone can help shed some light on the issue.
This is because fact that you do $em->flush() means that database operation is performed but it does not mean that your $urlGroup object is refreshed - php still has its state before flush has been performed, meaning with 0 urls.
Try to call:
$em->refresh($urlGroup)
right after $em->flush. This will refresh $urlGroup with info from database
I think that accepted answer is a bit misleading - yes, you will refresh the object with it's collection, but in my opinion this is a workaround. What generally flush gives are the identifiers (id's) to your model.
You are calling $url->setUrlGroup($urlGroup); in the owning side, but not updating the inverse side. What needs to be done in setUrlGroup method in Url class:
public function setUrlGroup($group)
{
$this->urlGroup = $group;
$group->addUrl($this);
}
And respectfully in UrlGroup class:
public function addUrl($url)
{
if (!$this->$urls->contains($url) {
$this->$urls->add($url);
}
}
Supposedly $urls above is instance of ArrayCollection.
This fixes the relation on the inverse side of your mapping. I would strongly recommend doing this way.

Doctrine Batch insert - entity manager clear

I would like to insert 10 000 rows to database with batch processing.
In first step I need select some objects from databse, then interate these objects and for each of them persist another object to database.
Here is code example:
$em = $this->getDoctrine()->getManager();
$products = $em->getRepository('MyBundle:Product')->findAll(); // return 10 000 products
$category = $em->getRepository('MyBundle:Category')->find(1);
$batchsize = 100;
foreach ($products as $i => $product) {
$entity = new TestEntity();
$entity->setCategory($category);
$entity->setProduct($product); // MyEntity And Product is OneToOne Mapping with foreign key in MyEntity
$em->persist($entity);
if ($i % $batchsize === 0) {
$em->flush();
$em->clear();
}
}
$em->flush();
$em->clear();
It returns this error:
A new entity was found through the relationship 'Handel\GeneratorBundle\Entity\GenAdgroup#product' that was not configured to cascade persist operations for entity
I think problem is in clear(), that remove all objects in memory including $products and $category.
If I use cascade={"persist"} in association, doctrine insert new category row in db.
After some attempts I made some dirty entity errors.
Am I doing sometihng wrong? What is solution and best practice for this job?
Thanks a lot for answer
solution is just to clear only those objects that are changing/creating. Those one that are constant should be left within EntityManager.
Like this
$em->clear(TestEntity::class);
$em->clear(...);
If you left clear without param it will detach all objects that are current under entity manager. Meaning that if you try to reuse them it will throw error as you get. For instance unique filed will be duplicated and trow that error.
After calling
$em->clear();
Category object becomes unpersisted.
You can try calling $em->merge($category) method on it. But probably the most guaranteed way is to fetch it again.
if ($i % $batchsize === 0) {
$em->flush();
$em->clear();
$category = $em->getRepository('MyBundle:Category')->find(1);
}

Make Doctrine to add entities in original order

I have following code:
$em = $this->getDoctrine()->getManager();
$evaluation->getQuestions()->clear();
foreach ($questions_data as $data) {
$id = (int) $data['id'];
if ($id > 0) {
$question = $em->getRepository('MyBundle:Question')->find($id);
if ($question)
$evaluation->getQuestions()->add($question);
}
}
$em->persist($evaluation);
$em->flush();
Here is $questions_data — array(['id' => 2], ['id' => 1], ['id' => 3]).
And here is how doctrine persist questions to database:
So, how to make doctrine to don't sort questions?
Evaluation entity has ManyToMany relation with the Question entity, so ORDER BY couldn't help, because table evaluations_questions was created automatically by Doctrine and don't have field id.
When you flush newly persisted items in Doctrine, Doctrine must determine which order to commit them to the database using an internal function getCommitOrder. The purpose of that function is to ensure that an object's dependencies are committed before the object itself is committed. This is done to comply with any foreign key constraints that might be set up. As you observed, a consequence of ordering data to commit like this is that you lose the ability to finely tune the order that items are committed - this isn't necessarily a bad thing.
In SQL the only way you can order your results is by issuing a query with ORDER BY. If you choose not to specify a sorting method, you cannot expect the results to come back in any particular order. The PostgreSQL docs explain this:
If sorting is not chosen, the rows will be returned in an unspecified order. The actual order in that case will depend on the scan and join plan types and the order on disk, but it must not be relied on.
In other words, it shouldn't matter what order the content is stored in your database.
In your problem, the order by which questions appear to a user is an issue. You can't allow the questions to appear randomly in an evaluation - they must follow a preset order. Frankbeen touches on this in a comment, but the best solution would be to add a new field on the Evaluation that stores an array of the Questions in the proper order. The order can then be read when you present the evaluation to a user.
If you absolutely must order them in a specific order in your database, you should be able to just flush the new objects individually as they are persisted instead of scheduling them to be flushed together.
$em = $this->getDoctrine()->getManager();
$evaluation->getQuestions()->clear();
foreach ($questions_data as $data) {
$id = (int) $data['id'];
if ($id > 0) {
$question = $em->getRepository('MyBundle:Question')->find($id);
if ($question) {
$evaluation->getQuestions()->add($question);
$em->persist($evaluation);
$em->flush();
}
}
}
Please be aware, this will take much more time to complete and is a pretty poor solution to your problem.
new records are stored at the end of the table, always. If you want to get the questions in natural order (by id) then you should use ORDER BY. I guess that the Evaluation entity has a OneToMany relation with the Question entity.
In that case you can simple add a optional annotation above the $questions property in the Evalutation entity:
/**
* #ORM\OneToMany(targetEntity="Question")
* #ORM\OrderBy({"id" = "ASC"})
*/
private $questions;
The EntityManagerInterface have transaction support and I recommend using that feature for all multi statement operations. This will allow you to create multiple statements and commit them in order all att once. Each statement will be executed in the order that they were added and if one statement fails, no changes will be done to the database at all.
That being said, all database implementations might not store data in the order that they were added, so if data is expected to be returned in a certain order, you should always query with ORDER BY explicitly as mentioned in another reply.
Say that you want to abort the operation if you encounter a question with an unknown id. This can quite easily be accomplished with something like the following:
function storeQuestions($questions_data): bool
{
$em = $this->getDoctrine()->getManager();
$em->beginTransaction();
$evaluation->getQuestions()->clear();
foreach ($questions_data as $data) {
$id = (int) $data['id'];
if ($id > 0) {
$question = $em->getRepository('MyBundle:Question')->find($id);
if ($question) {
$evaluation->getQuestions()->add($question);
$em->persist($evaluation);
} else {
$em->rollback();
return false;
}
}
}
$em->flush();
$em->commit();
return true; // Success
}

Delete records in Doctrine

I'm trying to delete a record in Doctrine, but I don't know why it's not deleting.
Here is my Code:
function del_user($id)
{
$single_user = $entityManager->find('Users', $id);
$entityManager->remove($single_user);
$entityManager->flush();
}
Plus: How can I echo query to see what going on here?
This is an old question and doesn't seem to have an answer yet. For reference I am leaving that here for more reference. Also you can check the doctrine documentation
To delete a record, you need to ( assuming you are in your controller ):
// get EntityManager
$em = $this->getDoctrine()->getManager();
// Get a reference to the entity ( will not generate a query )
$user = $em->getReference('ProjectBundle:User', $id);
// OR you can get the entity itself ( will generate a query )
// $user = $em->getRepository('ProjectBundle:User')->find($id);
// Remove it and flush
$em->remove($user);
$em->flush();
Using the first method of getting a reference is usually better if you just want to delete the entity without checking first whether it exists or not, because it will not query the DB and will only create a proxy object that you can use to delete your entity.
If you want to make sure that this ID corresponds to a valid entity first, then the second method is better because it will query the DB for your entity before trying to delete it.
For my understanding if you need to delete a record in doctrine that have a doctrine relationship eg. OneToMany, ManyToMany and association cannot be easy deleted until you set the field that reference to another relation equal to null.
......
you can use this for non relation doctrine
$entityManager=$this->getDoctrine()->getManager();
$single_user=$this->getDoctrine()->getRepository(User::class)->findOneBy(['id'=>$id]);
$entityManager->remove($single_user);
$entityManager->flush();
but for relation doctrine set the field that reference to another relation to null
$entityManager=$this->getDoctrine()->getManager();
$single_user=$this->getDoctrine()->getRepository(User::class)->findOneBy(['id'=>$id]);
{# assume you have field that reference #}
$single_user->setFieldData(null);
$entityManager->remove($single_user);
$entityManager->flush();
do you check your entity as the good comment annotation ?
cascade={"persist", "remove"}, orphanRemoval=true
In a Silex route I do like this, in case it helps someone:
$app->get('/db/order/delete', function (Request $request) use ($app) {
...
$id = $request->query->get('id');
$em = $app['orm.em']; //or wherever your EntityManager is
$order = $em->find("\App\Entity\Orders",$id); //your Entity
if($order){
try{
$em->remove($order);
$em->flush();
}
catch( Exception $e )
{
return new Response( $e->getMessage(), 500 );
}
return new Response( "Success deleting order " . $order->getId(), 200 );
}else{
return new Response("Order Not Found", 500);
}
}
You first need repository.
$entityManager->getRepository('Users')->find($id);
instead of
$single_user = $entityManager->find('Users', $id);
'Users' String is the name of the Users repository in doctrine ( depends if you are using Symfony , Zend . . etc ).
First, You may need to check if 'Users' is your fully qualified class name. If not check, and update it to your class name with the namespace info.
Make sure the object returned by find() is not null or not false and is an instance of your entity class before calling EM's remove().
Regarding your other question, instead of making doctrine return SQL's I just use my database (MySQL) to log all queries (since its just development environment).
try a var_dump() of your $single_user. If it is "null", it doens't exist ?
Also check if "Users" is a valid Entity name (no namespace?), and does the $id reference the PK of the user?
If you want to see the queries that are executed check your mysql/sql/... log or look into Doctrine\DBAL\Logging\EchoSQLLogger

Categories