Symfony/Doctrine - php

I am currently learning Symfony and Doctrine by reading the docs.
I don't understand the difference between find and findOneById. I tried to use them both in this simple example and it looks they do the same thing to me.
$product = $this->getDoctrine()
->getRepository('AcmeStoreBundle:ProductEntity')
->findOneById($id);
Are they really the same thing or there is some difference? And where I can find the detailed documentation for all these methods?

In your case, they happen to do the same thing. Looking at this example, you'll notice that find() looks for the field named after the primary key. findOneBy<Field>() will explicitly use the field in the name of the method, even if it's not the primary key, and will return the first record. So, in the end, if the primary key is indeed named id, then both will do the same thing.
// query by the primary key (usually "id")
$product = $repository->find($id);
// dynamic method names to find based on a column value
$product = $repository->findOneById($id);
$product = $repository->findOneByName('foo');

There is an API here I don't think there is any difference: the two methods, when call the way you call them, do this:
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id);
But find will be quicker and far quicker in some cases, because it doesn't use the __call magic method, and because find() checks a map of the current unit of work before whereas load() doesn't (see the #todo):
/**
* Loads an entity by a list of field criteria.
* ...
*
* #todo Check identity map? loadById method? Try to guess whether $criteria is the id?
*/
public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0)
So prefer find(), findOneById() is just a less efficient method to do the same thing.

In fact, is not the same thing.
Think about it. If you call "findBy()" you assume you'll receive a collection of entities ( 0, 1 or more than one ). So, to get all results, you'll need to iterate ArrayCollection or just get first ( $result->first() ).
If your query is by a unique key ( As this case ), you can just get unique entity by calling "getOneById()" and you will receive the entity as result.
/**
* Retrieving Product with 'findOneBy'
*/
$product = $this->getDoctrine()
->getRepository('AcmeStoreBundle:ProductEntity')
->findOneById($id);
/**
* Retrieving Product with 'findBy'
*/
$product = $this->getDoctrine()
->getRepository('AcmeStoreBundle:ProductEntity')
->findById($id)
->first();
Semantically, the first one it the best.
*TIP
Entity should be called just Product.
Why? Because is under "/Entity" folder ( Almost, should... ), and namespace will contain info about "What is exactly Product"

// query by the primary key (usually "id")
$product = $repository->find($id);
// dynamic method names to find based on a column value
$product = $repository->findOneById($id);
// $foo is any name which you want to find from database
$product = $repository->findOneByName($foo);

It calls the same method in the end.
findByKey('value')
Is basically the same as
findBy(array('key' => 'value'))
Where key is the property of the entity and value is the value of the property.
findById($id)
Is a special case of the above. And so is
find($id)
All of these methods execute the same query in the end. However, there is a difference in
findBy()
and
findOneBy()
Where findOneBy() only returns a single result and findBy will return all the results satisfying the demands.
However, in general it is considered good practice to use DQL queries instead. Consider lazy loading, array hydration, prepared statements, etc.
This is an interesting article on the topic:
Some Doctrine 2 Best Practices

Is the same thing, but I prefer the findOneBy method. It's more clear.

Related

Use search function on a Laravel collection of objects to return the key?

Generally speaking, I am trying to remove an object from a laravel collection of objects. I do not wish to convert the collection to an array to do this task, as this invalidates any real reason to use collections in the first place. Nor do I wish to delete the underlying model from the database - I just wish to remove the record from the collection.
Looking at the docs for available methods I don't see a simple way to complete this task.
Laravel Collections have a variety of methods. The search method looks very promising,
The search method searches the collection for the given value and returns its key if found.
I was planning to use the returned key with the forget method to dispose of the unwanted object.
The forget method removes an item from the collection by its key
However, every example I can find for the "search" method uses only a simple collection of integers or strings to show functionality. I am looking to search within the objects included in the collection.
We have the following variables:
$coll // a collection of objects taken from a database.
// Each object contains a field called "invoice_number" that I am trying to match.
$invoice_number // the invoice number associated with the object
// I wish to remove from $coll
$tmp_object = $coll->firstWhere('invoice_number', $invoice_number); // the needle for the haystack
Any assistance in finding a solution to the problem using collections is appreciated, especially using the "search" method.
Thank you.
you can use the "filter" method :
$coll = $coll->filter(function($invoice) use ($invoice_number){
return $invoice->invoice_number != $invoice_number;
});
if your invoice_number is unique, you can do this :
$coll = $coll->keyBy('invoice_number');
$coll[$invoice_number];
to retrieve it and
$coll->forget($invoice_number);
to only remove it from your collection
you can use filter:
$res = $coll->filter(function($el) use($invoice_number){
return $el->invoice_number != $invoice_number;
});

Symfony4 - How to update a doctrine ArrayCollection ?

I have a member of my entity is an arrayCollection. With a classic form builder is working fine, I can select multiple items and persist it. But when I try to update an object in controller I get the error : "Call to a member function setFaavailability() on array".
A resume of my entity :
/**
* #ORM\ManyToOne(targetEntity="App\Entity\FaAvailability",
inversedBy="faavailability")
* #ORM\JoinColumn(nullable=true)
* #ORM\Column(type="array")
*/
public $faavailability;
/**
* #return mixed
*/
public function getFaavailability()
{
return $this->faavailability;
}
/**
* #param mixed $faavailability
*/
public function setFaavailability($faavailability)
{
$this->faavailability = $faavailability;
}
In my controler :
$varFaavailability = $animal->faperson->getFaavailability();
foreach($varFaavailability as $availability){
if($availability->getName() == $animal->typepet->getName()){
$varFaavailability->removeElement($availability);
$faPerson = $em->getRepository(FaPerson::class) >findById($animal->faperson->getId());
$faPerson->setFaavailability($varFaavailability);
$em->persist($faPerson);
$em->flush();
}
}
Any ideas ?
If I remember well, when you set a field as an ArrayCollection it means that you have a oneToMany relationship between two entities.
From your code, I can tell you that you are trying to persist the data in the wrong entity. You usually add the owning_entity_id(1-to-N) in each item(1-to-N) and persist it. In your code, you are trying to set all the references at once, which is never going to happen. Delete the setFaavailability() or redefine the entities' relationships.
You should never try to mass-add foreign key relationships in one super duper setter function. Cycle through all the items and set the reference to the "parent" entity.
The problem is in this part: $faPerson = $em->getRepository(FaPerson::class)->findById($animal->faperson->getId());
The findBy* methods will try to find multiple entities and return them in a Collection.
If you're looking for a single person, you can use findOneById instead. Or (assuming id is configured as identifier in Doctrine) you can even use the find method: $faPerson = $em->getRepository(FaPerson::class)->find($animal->faperson->getId());
some general comments:
In Doctrine you never have to work with the IDs. Use the entity
objects! You only need to findById if you get the ID from a request parameter for example.
You should reconsider the naming of your variables to make it clear if it is a collection ($availabilities) or a single one ($availability).
Always use the getter/setter methods instead of the fields (typepet vs getTypepet()).
Call flush() one at the end to update all entities in one single transaction.
I've renamned the variables below as I understood them. However I am still not sure what $animal->faperson->getFaavailabilities() returns, since at the beginning you wanto to loop through the results and later set it to a single one via setFaavailability()?
//Should be a Doctrine ArrayCollection
$varFaavailabilities = $animal->faperson->getFaavailabilities();
foreach($varFaavailability as $availability){
if($availability->getName() == $animal->getTypepet()->getName()) {
//Why do you want to remove an element from the current loop?
$varFaavailability->removeElement($availability);
//No need to use Id
$faPerson = $animal->getFaperson();
//A single one?
$faPerson->setFaavailability($availability);
//More than one? addFaavailability should exist.
$faPerson->addFaavailability($availability);
$em->persist($faPerson);
}
}
$em->flush();

Doctrine: How to order by attribute from association with Criteria

I am not able to order records by attribute from another entity.
I have class Employee, which has attribute $user, which points to class User, which have attribute $active.
Now I want to sort Employee by user.active and I am not able to do that. This is how I call the em:
/**
* #param Criteria $criteria
* #param array $order_by
* #param integer $limit
* #param integer $offset
* #return \Doctrine\Common\Collections\Collection
*/
public function findByCriteria($criteria, $order_by = null, $limit = null, $offset = null) {
$criteria->setFirstResult($offset);
$criteria->setMaxResults($limit);
$criteria->orderBy($order_by);
$result = $this->repository->matching($criteria);
return $result;
}
I inspected BaseEntityPersister.php and it seems like there is no implementation of such a thing. It just checks if user.active is an attribute of Employee class and throws
Doctrine\ORM\ORMException
Unrecognized field: user.active
I know I can do that via QueryBuilder and joins, but I want to make my code more reusable and Criteria seemed like a good choice.
Thank you for your advices!
EDIT:
If I use findBy, there is no problem with sorting field user.active. Should I consider this a limitation of matching method? It is sad, because I need to use Doctrine\Common\Collections\Criteria. I can use findBy with order and then use matching method to filter records, but I would rather do that on the database side.
EDIT 2:
I use Nette with Kdyby/Doctrine. Didn't know that user.active is implemented in Kdyby/doctrine and not in Doctrine directly. So I suppose this question won't be answered..
If you look at Kdyby/Doctrine, is extends the findBy capabilities by automatically detecting relations and performing join if needed as cane seen here by calling autoJoinOrderBy.
This is the reason why criteria does not support join, and Kdyby's findBy does.
If you want reusable way to build Doctrine queries, the Criteria does not help you with that, but there is QueryObject, which does the similar job and lets you reuse logical query parts, but using QueryBuilder.
It is not so well documented, but here are some resources:
Official documentation of QueryObject
Blog post with example usage
Kdyby/Doctrine autor's presentation on Doctrine and QueryObject (from slide 43)
Some more official information about QueryObjects

CakePHP converts ID field to $this->id in model

CakePHP 2.6.x
I used the bake CLI to create my models, which created fields named ID. Notice it's uppercase.
So in my models, I was expecting to reference that property like this: $this->ID, as the property names usually match the field names (in my experience). It's definitely working that way in the controllers. For example, I have lots of controller code that looks like this:
$this->SomeModel->findById( $model['SomeModel']['ID'] );
However this didn't work in the model. After a lot of head scratching and experimenting, I finally figured out that the model property is named id (notice the lower case).
//in SomeModel.php
var_dump( $this->ID ); //NULL
var_dump( $this->id ); 33
Is this the expected behavior? Do all model properties get converted to lower case? If so, why is the controller different? Did I defy a CakePHP convention somehow? Any explanation of what is going on here would be most welcome.
When you call $this->id you're accessing the Model's id property, not the value of the field in the database.
From source;
<?php
/**
* Value of the primary key ID of the record that this model is currently pointing to.
* Automatically set after database insertions.
*
* #var mixed
*/
public $id = false;
As Mark's suggested in his comment, use $this->primaryKey = 'ID' in your model to achieve the desired result, you could then do something like this in 2.6:
<?php
$this->id = 33; // Set the active record
$this->field('ID'); // Returns 33 (If you really want to use uppercase)
$this->id; // Returns 33
$this->read(); // Returns all of record 33

Does the Yii CActiveRecord class have a first() method?

I need to just get the first record from a Yii CActiveRecord derived class. In Rails I would just be able to do this:
post = Post.first
I thought I could do the same thing with Yii like this:
$post = Post::model()->first();
But that method doesn't exist. Do I have to just do find with a condition to get the first record?
I don't see first() in the docs for CActiveRecord so I assume the answer is no, it doesn't have a first method. So how would one go about querying just the first record?
This works but sure is an ugly hack. Surely there's a better way.
$first = Post::model()->findAll(array('order'=>id, 'limit'=>1));
Yii isn't going to make any assumptions about how your data should be ordered. Good database design requires that if you use a surrogate key, that key should have a meaningless value. That means NOT using it for ordering.
That issue aside, here is probably the best way to do your query:
$first = Post::model()->find(array('order'=>'id ASC'));
By using find instead of findAll you automatically apply a LIMIT 1 to your result. I would not skip the inclusion of the order by clause, as that insures that the database will order the results consistently.
If you use this query a lot, you can create the following method. UPDATE: Modified it to throw an exception when the primaryKey is composite or missing. We could add more error checking as well, but we leave that as an exercise for the reader. ;)
public function first($orderBy = null){
if(!$orderBy){
$orderBy = self::model()->tableSchema->primaryKey;
}
if(!is_string($orderBy)){
throw new CException('Order by statement must be a string.');
}
return self::model()->find(array('order'=>$orderBy));
}
Then include this method in a class which extends CActiveRecord, and then extend all your models form that class.
The wrapper I wrote will by default order results by the primary key, but you could optionally pass a different column and direction (ASC OR DESC) if you wish.
Then if you do this for the post class, you can access the first model like so:
$first = Post::model()->first();
CActiveRecord::find() returns only one model.
$first=Post::model()->find();
Yii2 asks for a condition when doing a findOne().
You could do a find() following with no conditions and just return one()
$first= Post::find()->one();
To really be sure you could just add a orderBy clause to it:
$first= Post::find()->orderBy(['id' => SORT_ASC])->one();
Same goes for the command function:
$first= \Yii::$app->myDatabase->createCommand('SELECT * FROM Post ORDER BY id ASC')->queryOne();

Categories