Symfony Doctrine exclude persisted entity - php

I have an entity Key (Not the real name, I know Key is forbidden) and I need, in a loop, get a Key with state=1, and change it to state=2. This is my script :
/* Each object */
for ($i=0; $i < $order->getQuantity(); $i++) {
/* get available key */
$key = $this->getDoctrine()->getRepository('AppBundle:Key')->findOneBy(array('state' => 1));
$key->setState(2); // On la rend active
$this->_em()->persist($key);
}
}
My probleme is with this line : $key = $this->getDoctrine()->getRepository('AppBundle:Key')->findOneBy(array('state' => 1));
Doctrine always get the same first key with state=1. If I flush directly in the loop it's ok, but I can have a very big loop and I don't want to flush XXXX times.
Is there a way to don't get already persisted entity ? How can I say to Doctrine to get a Key with state=1 ONLY if I don't already persisted ?
Thanks !

Why don't you do this:
$keys = $this->getDoctrine()->getRepository('AppBundle:Key')->findBy(array('state' => 1));
foreach($keys as $key) {
$key->setState(2);
$this->_em()->persist($key);
}
$this->_em()->flush();
Thereby each key will only be persisted once and because persisting things is symfony logic only you have only one DB write action during the flush-function where all persisted items will be stored

In addition to retrieving and looping over your entities, you can also use DQL (unless I am missing context from your question that precludes this).
For example:
$dql = 'UPDATE AppBundle:Key k SET k.state = 2 WHERE k.state = 1';
$query = $this->_em->createQuery($dql);
$result = $query->getResult();
This is untested obviously. It's been a while since I wrote DQL so you might want to consult the docs. Hope this helps :)

Persisting means "Hey Doctrine, let's be aware of that entity instance!".
It's used (as you surely already know) when you create a new entity instance ($key = new Key();), and then you want Doctrine to be aware of it ($em->persist($key);) to be able to add a new record in the database on flush ($em->flush()).
All entity instances retrieved with Doctrine are already persisted (Doctrine is already aware of them).
So, in your code, the persist call is useless. And as you don't flush, the database is not updated.
Then, in the next loop, when you request from the database (->findOneBy(...)), you will get again the same entity instance, with state still equals to 1.
Finally, to answer your questions "Is there a way to don't get already persisted entity ? How can I say to Doctrine to get a Key with state=1 ONLY if I don't already persisted ?":
No, it's just impossible.

Related

How to persist cloned entity object in Symfony/Doctrine

I am trying to clone an entity record along with the relationships it holds among other entities. I have successfully cloned some entity objects but this one to many entity relationship has challenged me. I have reviewed similar questions regarding the error message I have been given without progress to the challenge.
The correct records are queried out, looped through and cloned then stored in an array. I have tried to persist the array but get error
EntityManager#persist() expects parameter 1 to be an entity object,
array given
I then tried to encode the array and persist but I get error
The class 'Symfony\Component\HttpFoundation\JsonResponse' was not
found in the chain configured namespaces NameOfBundle\Entity.
This below code is in my controller
$quoteItemAddWorkCollection = $em->getRepository('UniflyteBundle:QuoteItemAdditionalWork')->findBy($params);
$quoteItemDeliverableCollection = $em->getRepository('UniflyteBundle:QuoteItemDeliverable')->findBy($params);
if (!empty($quoteItemAddWorkCollection)) {
$quoteItemAddWorkArray = [];
foreach ($quoteItemAddWorkCollection as $quoteItemAddWorkItem) {
$quoteItemAddWorkItemClone = clone $quoteItemAddWorkItem;
array_push($quoteItemAddWorkArray, $quoteItemAddWorkItemClone);
}
$quoteItemAddWorkCollection = new JsonResponse($quoteItemAddWorkArray);
$em->persist($quoteItemAddWorkCollection);
I can't persist an array, I have to encode it to json first I believe. What am I doing wrong?
I think you have a misunderstanding of Doctrine concepts here. In terms of Doctrine, each entity:
UniflyteBundle:QuoteItemAdditionalWork
and
UniflyteBundle:QuoteItemDeliverable
, and any of its relationships, could get persisted, using a configuration named Mapping.
To get this into work, any In-Memory object, MUST be an instance of a managed entity class.
There is not such a magic in Doctrine, to persist so many unknown objects at once. You may persist them, one-by-one inside a loop:
foreach ($quoteItemAddWorkCollection as $quoteItemAddWorkItem) {
$quoteItemAddWorkItemClone = clone $quoteItemAddWorkItem;
$quoteItemAddWorkItemClone->setId(null);
// Set relationships here ...
$em->persist($quoteItemAddWorkItemClone);
}
Keep in mind to set any required relationships, before persisting your new cloned objects.
If you want to use, one persist, you can assign their relationships, inside a loop:
foreach ($quoteItemAddWorkCollection as $quoteItemAddWorkItem) {
$quoteItemAddWorkItemClone = clone $quoteItemAddWorkItem;
$quoteItemAddWorkItemClone->setId(null);
$someParentCollection->add($quoteItemAddWorkItemClone);
}
$em->persist($someParentCollection);
the latter method, needs you to set cascade on mapping configuration:
class SomeParent
{
// #ORM\OneToMany(targetEntity="QuoteItemAdditionalWork", mappedBy="parent", cascade={"persist"})
private $quoteItemAddWork;
}

How to simplify this Laravel PHP code to one Eloquent query?

I assume that this should all be in one query in order to prevent duplicate data in the database. Is this correct?
How do I simplify this code into one Eloquent query?
$user = User::where( 'id', '=', $otherID )->first();
if( $user != null )
{
if( $user->requestReceived() )
accept_friend( $otherID );
else if( !$user->requestSent() )
{
$friend = new Friend;
$friend->user_1= $myID;
$friend->user_2 = $otherID;
$friend->accepted = 0;
$friend->save();
}
}
I assume that this should all be in one query in order to prevent
duplicate data in the database. Is this correct?
It's not correct. You prevent duplication by placing unique constraints on database level.
There's literally nothing you can do in php or any other language for that matter, that will prevent duplicates, if you don't have unique keys on your table(s). That's a simple fact, and if anyone tells you anything different - that person is blatantly wrong. I can explain why, but the explanation would be a lengthy one so I'll skip it.
Your code should be quite simple - just insert the data. Since it's not exactly clear how uniqueness is handled (it appears to be user_2, accepted, but there's an edge case), without a bit more data form you - it's not possible to suggest a complete solution.
You can always disregard what I wrote and try to go with suggested solutions, but they will fail miserably and you'll end up with duplicates.
I would say if there is a relationship between User and Friend you can simply employ Laravel's model relationship, such as:
$status = User::find($id)->friends()->updateOrCreate(['user_id' => $id], $attributes_to_update));
Thats what I would do to ensure that the new data is updated or a new one is created.
PS: I have used updateOrCreate() on Laravel 5.2.* only. And also it would be nice to actually do some check on user existence before updating else some errors might be thrown for null.
UPDATE
I'm not sure what to do. Could you explain a bit more what I should do? What about $attributes_to_update ?
Okay. Depending on what fields in the friends table marks the two friends, now using your example user_1 and user_2. By the example I gave, the $attributes_to_update would be (assuming otherID is the new friend's id):
$attributes_to_update = ['user_2' => otherID, 'accepted' => 0 ];
If your relationship between User and Friend is set properly, then the user_1 would already included in the insertion.
Furthermore,on this updateOrCreate function:
updateOrCreate($attributes_to_check, $attributes_to_update);
$attributes_to_check would mean those fields you want to check if they already exists before you create/update new one so if I want to ensure, the check is made when accepted is 0 then I can pass both say `['user_1' => 1, 'accepted' => 0]
Hope this is clearer now.
I'm assuming "friends" here represents a many-to-many relation between users. Apparently friend requests from one user (myID) to another (otherId).
You can represent that with Eloquent as:
class User extends Model
{
//...
public function friends()
{
return $this->belongsToMany(User::class, 'friends', 'myId', 'otherId')->withPivot('accepted');
}
}
That is, no need for Friend model.
Then, I think this is equivalent to what you want to accomplish (if not, please update with clarification):
$me = User::find($myId);
$me->friends()->syncWithoutDetaching([$otherId => ['accepted' => 0]]);
(accepted 0 or 1, according to your business logic).
This sync method prevents duplicate inserts, and updates or creates any row for the given pair of "myId - otherId". You can set any number of additional fields in the pivot table with this method.
However, I agree with #Mjh about setting unique constraints at database level as well.
For this kind of issue, First of all, you have to enjoy the code and database if you are working in laravel. For this first you create realtionship between both table friend and user in database as well as in Models . Also you have to use unique in database .
$data= array('accepted' => 0);
User::find($otherID)->friends()->updateOrCreate(['user_id', $otherID], $data));
This is query you can work with this . Also you can pass multiple condition here. Thanks
You can use firstOrCreate/ firstOrNew methods (https://laravel.com/docs/5.3/eloquent)
Example (from docs) :
// Retrieve the flight by the attributes, or create it if it doesn't exist...
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
// Retrieve the flight by the attributes, or instantiate a new instance...
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);
use `firstOrCreate' it will do same as you did manually.
Definition of FirstOrCreate copied from the Laravel Manual.
The firstOrCreate method will attempt to locate a database record using the given column / value pairs. If the model can not be found in the database, a record will be inserted with the given attributes.
So according to that you should try :
$user = User::where( 'id', '=', $otherID )->first();
$friend=Friend::firstOrCreate(['user_id' => $myId], ['user_2' => $otherId]);
It will check with both IDs if not exists then create record in friends table.

Symfony Criteria Join unexpected behaviour

i'm trying to do a complex query with Criteria in a Symfony project using Propel ORM.
the query i want to make is, in human words:
Select from the 'interface' table the registers that:
- 1 are associated with a process (with a link table)
- 2 have a name similat to $name
- 3 its destiny application's name is $apd (application accecible by foreign key)
- 4 its originapplication's name is $apo (application accecible by foreign key)
here the code i made, and not working:
$c = new Criteria();
$c->addJoin($linkPeer::CODIGO_INTERFASE,$intPeer::CODIGO_INTERFASE); //1
$c->add($linkPeer::CODIGO_PROCESONEGOCIO,$this->getCodigoProcesonegocio());//1
if($name){
$name = '%'.$name.'%'; //2
$c->add($intPeer::NOMBRE_INTERFASE,$name,Criteria::LIKE); //2
}
if($apd){
$apd = '%'.$apd.'%'; //3
$c->addJoin($appPeer::CODIGO_APLICACION,$intPeer::CODIGO_APLICACION_DESTINO);//3
$c->add($appPeer::NOMBRE_APLICACION,$apd,Criteria::LIKE); //3
}
if($apo){
$apo = '%'.$apo.'%';//4
$c->addJoin($appPeer::CODIGO_APLICACION,$intPeer::CODIGO_APLICACION_ORIGEN);//4
$c->add($appPeer::NOMBRE_APLICACION,$apo,Criteria::LIKE);//4
}
After that i did a $c->toString() to see the SQL generated and i saw that when i send only an $apd value, the SQL is correct, when i send an $apo value too. But when i send both, only the $apo AND apears on the SQL.
I guess its because the $c->add(...) call is the same with a distinct parameter, but not sure at all. Is this the error? What is the best way to generate my query correctly?
Thank you very much for your time! :D
Yes, it's overriding the previous call as the Criteria object only stores one condition per field. The solution is to create 2 or more separate Criterion objects and mix them into the Criteria object:
//something like this
$cron1 = $criteria->getNewCriterion();
$cron1->add($appPeer::NOMBRE_APLICACION,$apo,Criteria::LIKE);//4
$criteria->add($cron);
//and the same with the other criterion
However, it would be much easier to upgrade to Propel15+, where you work on the Query class level, and multiple restrictions on the same field don't override each other.
Hope this helps,
Daniel

Doctrine : do not load related records

In order to improve performance of app, I would like to separate queries instead of using leftJoins. Then I have to create my own related Doctrine_Collection :
$user->Friends->add($current_friend);
But I don't want doctrine does a query when I try to access related (not loaded) Collection.
How I can do that.
Thanks in advance.
I think the answer is in this ยง about relation handling. Build a new friendship relation and save it instead of adding a friend to a user object.
then I found this way (I should optimize this) :
$my_relation_collFriend = FriendTable::getInstance()->findByIdUser($user->id_user);
foreach($my_relation_collFriend as $friend)
{
$collFriend = $user->get('Friends', false); //get the related collection without db query
if(!$collFriend ) //unfornatly, It can be null
{
$collFriend = new Doctrine_Collection::create('friend'); //create the collection
$user->set('Friends', $collFriend, false); // define the related collection without db query
}
$collFriend->add($friend); //add the record to related collection
}
With this example I know this is useless but with lot of joins and datas it becomes necessary

Update object instead of inserting with Doctrine using assignIdentifier()

I'm pretty new to Doctrine, but as I understand it, the assignIdentifier() method is supposed to tell Doctrine to update the relevant row into the database instead of inserting a new one.
I have an object that I'm building through a workflow, so the identifier has an id of null until I call $object->save(); which inserts it, and this does work.
If however I call $object->assignIdentifier($newobj->id); and then $object->save(); it does nothing - it does not insert a new row and does not update the old one.
If a certain condition is true, I want to pull a different record out of the DB and update that row instead of inserting the new one.
Am I understanding something wrong here?
Some code to illustrate:
if($this->object->payments > 0) {
$older_payment = Doctrine_Query::create()
->from('OldPaid p')
->where('p.dealid = ?', $this->object->transid)
->fetchOne()
;
$this->object->assignIdentifier($older_payment->id);
}
$this->object->save();
Like i got to know, save() will not update an existing record with autoincrement on ID.
I have the same problem using doctrine 1.2.
an idea i have use this one, the only workaroung i found:
$query = Doctrine_Query::create()->update('OldPaid');
$query->set($yourFieldname, '?', $yourValue);
$query->addwhere('p.dealid = ?', $this->object->transid);
$query->execute();
Thiw will function when a record is in the DN with the primaryKey dealid = $this->object->transid.
greeting m
Usually, if you retrieve a record, you can update it with the save() method. Doctrine recognizes this (since the PK doesn't change) and updates the record.
From the docs:
Updating objects is very easy, you
just call the Doctrine_Record::save()
method
Another way can be replace(), but I usually use just save() and does either the saving or the updating if the record already exists.
As far as I can read from the description of assignIdentifier() never used it myself) it will only work with retrieving an object by its ID, so updating something with this method will not work.

Categories