Doctrine: How to order by attribute from association with Criteria - php

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

Related

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();

Laravel Capsule set primary key

I want to implement a layer between my custom framework and Eloquent, in other words I want use the Eloquent as an ORM for my framework...
I started with this https://github.com/illuminate/database
As you can see looks quite simple, after booting up the Eloquent, the Capsule become available like this:
Capsule::table('users')->where('votes', '>', 100)->get();
Same way should work this:
Capsule::table('users')->find(3);
Where the Eloquent finds user id=3
But what would be, if the table primary key set something else like user_id
I know In Laravel I can set in model primary key
public $primaryKey = 'user_id';
But unfortunately, this is not my case, so I wander if Laravel developers does not implement something for other developers to make it easy to use Eloqunet for other frameworks or at least I can't find it out?
Please help me with this.. let me know if you need me to write more details...
You can't using just the query builder. You can look at the code of Query/Builder.php and see that the find method is hard coded with id:
/**
* Execute a query for a single record by ID.
*
* #param int $id
* #param array $columns
* #return mixed|static
*/
public function find($id, $columns = ['*'])
{
return $this->where('id', '=', $id)->first($columns);
}
Just use where('user_id', $id)->first() if you don't want to follow Laravel conventions and are just using the query builder.
Beyond that, Eloquent Models should function the way you want, but you wouldn't use Capsule for those queries.

Filter #OneToMany in Doctrine

I have a #OneToMany relationship:
/**
* #OneToMany(targetEntity="\MyApp\Entities\Content", mappedBy="clist")
* #OrderBy({"contentOrder"="ASC", "id"="ASC"})
*/
private $contents;
I get all the Content's, which clist attribute is the id of the current instance. But the Content class has an isDeleted attribute also. If it is set to true, I would like to exclude it from the list.
How is it possible?
I can filter the list in the getContents() function, but I hope there is a better solution. Maybe somehow in the definition of the #OneToMany relationship
Option 1
I recommend using Criteria to filter collections
use Doctrine\Common\Collections\Criteria;
$clist = $entityManager->find('Clist', $clistId);
$contentCollection = $clist->getContents();
$criteria = Criteria::create()
->where(Criteria::expr()->eq("isDeleted", false))
;
$undeletedContents = $contentCollection->matching($criteria);
Option 2
Another option could be to use Filters where you can make sure that as long as the filter is active each and every Query is making sure that a 'where' constraint is attached.
This usually makes sense if you have some attributes which are queried most of the time for most of your entities (like e.g. a ClientId or a Deleted attribute).
If you are using ArrayCollection to store your relations then you can do this:
$criteria = Criteria::create()->where(Criteria::expr()->eq("isDeleted", false));
$contents = $yourMainClass->getContents()->matching($criteria);
Hope this helps,
Alexandru Cosoi

Symfony/Doctrine

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.

How to retrieve an entity with all of its associations using EntityManager in Doctrine2?

I have a simple entity with many-to-many and one-to-many associations. I'm aware of 'Joins' for fetching related associations which is a manual solution for my problem.
How can I fetch an entity with all of its associations using EntityManager in Doctrine2? e.g.:
$this->em
->getRepository('Entities\Patientprofile')
->findOneByuserid('555555557')
->fetchAllAssociations();
from http://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html#temporarily-change-fetch-mode-in-dql
you can set eager fetch mode temporarily:
$query = $em->createQuery("SELECT u FROM MyProject\User u");
$query->setFetchMode("MyProject\User", "address", "EAGER");
$query->execute();
If you want do load dynamically all associations with this fetch mode, you can use the getAssociationMappings() method of the Doctrine\ORM\Mapping\ClassMetadataInfo, passing your entity name as parameter to the constructor of ClassMetadataInfo and then iterate over the returned array as $assoc and call:
$query->setFetchMode("MyProject\User", $assoc, "EAGER");
Doc: ClassMetadataInfo#getAssociationMappings()
Doctrine2 setFetchMode not working with "EAGER"
I tried also to fetch the associating entities "eagerly" using setFetchMode in my query, but the following didn't seem to work:
$query->setFetchMode("MyProject\User", "address", "EAGER");
When I jumped into the files I found out that the third parameter $fetchMode should be an integer. The constants are defined in Doctrine\ORM\Mapping:ClassMetadataInfo. When passing a string it will default to Mapping\ClassMetadata::FETCH_LAZY because of this if clause.
/**
* Specifies that an association is to be fetched when it is first accessed.
*/
const FETCH_LAZY = 2;
/**
* Specifies that an association is to be fetched when the owner of the
* association is fetched.
*/
const FETCH_EAGER = 3;
/**
* Specifies that an association is to be fetched lazy (on first access) and that
* commands such as Collection#count, Collection#slice are issued directly against
* the database if the collection is not yet initialized.
*/
const FETCH_EXTRA_LAZY = 4;
So setting the corresponding integer solved the problem:
$query->setFetchMode("MyProject\User", "address", 3);
Or declare the class use Doctrine\ORM\Mapping\ClassMetadata at the top and then use the constant:
$query->setFetchMode("MyProject\User", "address", ClassMetadata::FETCH_EAGER);
EDIT:
Since there seems to be a lot of confusion here on how to fetch associations the right way I will edit my answer and add some additional information on how you can fetch join using your repository.
According to the Doctrine documentation there are 2 types of joins:
Regular Joins: Used to limit the results and/or compute aggregate values.
Fetch Joins: In addition to the uses of regular joins: Used to fetch related entities and include them in the hydrated result of a
query.
So to get an entity including its associations you will need to "fetch-join" all these associations to make sure they are loaded eagerly.
I usually don't use DQL queries for getting entities and solving my fetch joins, instead I add a custom method to a repository where I use a query builder. This is more flexible and much more readable then using DQL. The correct DQL query will be created by the query builder when we call the createQuery method. You can check the created DQL query of course for debug purposes.
An example for such a custom method inside the Patientprofile entity repository from the question above:
public function findPatientByIdWithAssociations($id)(
// create a query builder for patient with alias 'p'
$qb = $this->createQueryBuilder('p')
->where('p.id = :patient_id')
->addSelect('pd')
->leftJoin('p.documentation', 'pd')
->addSelect('pa')
->leftJoin('p.address', 'pa')
->setParameter('patient_id', $id);
$query = $queryBuilder->getQuery();
return $query->getSingleResult();
}
And now you can use your custom repository method to get the patient by id (for example '555555557') including associations to the patient documentation and address:
$repository = $this->em->getRepository('Entities\Patientprofile');
$patient = $repository->findPatientByIdWithAssociations('555555557');
Make sure you use both addSelect and leftJoin to do eager loading.
Doctrine 2 uses Proxy classes for lazy loading, so you don't actually need to have the associations' data fetched until you use the objects. Since the Proxy classes inherit from your association classes, you're able to use the proxies exactly as you would use the fretch association classes.
but, if you really need to fetch the actual association classes, you need to tell the query to set the fetch mode to Doctrine\ORM\Mapping\ClassMetadata::FETCH_EAGER. If you're using the annotations, you can achieve this with:
e.g.
/**
* #ManyToMany(targetEntity="Item", fetch="EAGER")
*/
private $items;
You can use a DQL query:
$query = $em->createQuery("SELECT p, f FROM Entities\\Patientprofile p JOIN p.Foo f WHERE p.id = ?1");
$query->setParameter(1, 321);
$patient = $query->getSingleResult();
Faced the same problem.
It was necessary to pull out all chain of parents of an element.
$query->setFetchMode(EntityClass, "alias_in_entity", 3) gets only 1 lvl deep, other parents are just proxy.
This can be fixed by changed in entity class fetch mode to eager. But if it`s not if this is not possible for some reason (performance etc), this can be made as #wormhit mentioned by changing entity metadata "on fly"
Example:
$query = $this->entityManager->createQueryBuilder()->select('fields')
->from(FormField::class, 'fields');
$metadata = $this->entityManager->getClassMetadata(FormField::class);
$metadata->setAssociationOverride('parent', ['fetch' => \Doctrine\ORM\Mapping\ClassMetadata::FETCH_EAGER]);
return $query->getOneOrNullResult();

Categories