91

I'd need to use a "magic finder" findBy method using comparative criteria (not only exact criteria). In other words, I need to do something like this:

$result = $purchases_repository->findBy(array("prize" => ">200"));

so that I'd get all purchases where the prize is above 200.

xarlymg89
  • 2,552
  • 2
  • 27
  • 41
ElPiter
  • 4,046
  • 9
  • 51
  • 80

7 Answers7

226

The class Doctrine\ORM\EntityRepository implements Doctrine\Common\Collections\Selectable API.

The Selectable interface is very flexible and quite new, but it will allow you to handle comparisons and more complex criteria easily on both repositories and single collections of items, regardless if in ORM or ODM or completely separate problems.

This would be a comparison criteria as you just requested as in Doctrine ORM 2.3.2:

$criteria = new \Doctrine\Common\Collections\Criteria();
$criteria->where(\Doctrine\Common\Collections\Criteria::expr()->gt('prize', 200));

$result = $entityRepository->matching($criteria);

The major advantage in this API is that you are implementing some sort of strategy pattern here, and it works with repositories, collections, lazy collections and everywhere the Selectable API is implemented.

This allows you to get rid of dozens of special methods you wrote for your repositories (like findOneBySomethingWithParticularRule), and instead focus on writing your own criteria classes, each representing one of these particular filters.

yivi
  • 42,438
  • 18
  • 116
  • 138
Ocramius
  • 25,171
  • 7
  • 103
  • 107
  • 2
    Note: I'm using symfony 2.8.11 with doctrine and - maybe just there - it's "Criteria::expr()->gt()", not "$criteria->expr()->gt()". – Select0r Sep 30 '16 at 08:39
  • 4
    It's a static method: https://github.com/doctrine/collections/blob/2dfc898f2a66c192d6a195bdc9c5040f65adfaab/lib/Doctrine/Common/Collections/Criteria.php#L83 Also: Symfony is *NOT* Doctrine. Reference doctrine stuff with doctrine names and versioning :-P – Ocramius Sep 30 '16 at 22:23
  • @Ocramius then it should be `$criteria::expr()->gt()` ideally, no? – Adrian Föder Aug 24 '17 at 14:56
  • 3
    `Criteria::expr()` also OK - feel free to edit the answer. – Ocramius Aug 25 '17 at 09:57
  • I do get an error here. I try to loop over `$result` in a twig template and only get `Unrecognized field: 0` pointing to the line `{% for r in result %}`. – olidem Mar 23 '18 at 12:03
  • 4
    Having concrete repository methods like `findOneBySomethingWithParticularRule` is a good thing IMO as it decouples your business logic from Doctrine implementation details like the criteria builder. – David Dec 20 '19 at 15:41
34

This is an example using the Expr() Class - I needed this too some days ago and it took me some time to find out what is the exact syntax and way of usage:

/**
 * fetches Products that are more expansive than the given price
 * 
 * @param int $price
 * @return array
 */
public function findProductsExpensiveThan($price)
{
  $em = $this->getEntityManager();
  $qb = $em->createQueryBuilder();

  $q  = $qb->select(array('p'))
           ->from('YourProductBundle:Product', 'p')
           ->where(
             $qb->expr()->gt('p.price', $price)
           )
           ->orderBy('p.price', 'DESC')
           ->getQuery();

  return $q->getResult();
}
con
  • 2,358
  • 25
  • 33
  • 17
    Avoid using DQL if not strictly needed, it's just making your logic more and more coupled to the ORM. – Ocramius Jul 27 '14 at 12:22
  • 9
    @Sliq this is a doctrine behaviour and doesn't necessarily have anything to do with symfony. – con Mar 14 '17 at 07:08
  • 12
    @Sliq after you try some more frameworks, you'll realize that Symfony isn't so crappy – ononononon Mar 15 '17 at 14:17
  • If I see correctly, this function is a repository method. Here, you can go straight to `$this->createQueryBuilder('p')` instead of going around, via *EntityManager*: `$this->getEntityManager()->createQueryBuilder()`. – Gander Nov 20 '19 at 10:18
  • @Ocramius Can you expand on this please? (not using DQL -- isn't that the core of doctrine?) – Jordan May 03 '23 at 13:30
9

You have to use either DQL or the QueryBuilder. E.g. in your Purchase-EntityRepository you could do something like this:

$q = $this->createQueryBuilder('p')
          ->where('p.prize > :purchasePrize')
          ->setParameter('purchasePrize', 200)
          ->getQuery();

$q->getResult();

For even more complex scenarios take a look at the Expr() class.

dbrumann
  • 16,803
  • 2
  • 42
  • 58
  • 8
    Avoid using DQL where not strictly necessary. It locks you into the ORM specific API, and is not really re-usable. There are some cases where DQL is required, but this is not one of those. – Ocramius Feb 09 '13 at 16:21
  • 10
    how does using the QueryBuilder not lock you into doctrine in exactly the same way? – NDM Feb 25 '15 at 14:30
6
$criteria = new \Doctrine\Common\Collections\Criteria();
    $criteria->where($criteria->expr()->gt('id', 'id'))
        ->setMaxResults(1)
        ->orderBy(array("id" => $criteria::DESC));

$results = $articlesRepo->matching($criteria);
Mez
  • 24,430
  • 14
  • 71
  • 93
  • This is not working for me, see https://stackoverflow.com/questions/49450970/get-elements-from-lazyloadcollection – olidem Mar 23 '18 at 13:48
3

The Symfony documentation now explicitly shows how to do this:

$em = $this->getDoctrine()->getManager();
$query = $em->createQuery(
    'SELECT p
    FROM AppBundle:Product p
    WHERE p.price > :price
    ORDER BY p.price ASC'
)->setParameter('price', '19.99');    
$products = $query->getResult();

From http://symfony.com/doc/2.8/book/doctrine.html#querying-for-objects-with-dql

Jeff Clemens
  • 912
  • 9
  • 17
2

I like to use such static methods:

$result = $purchases_repository->matching(
    Criteria::create()->where(
        Criteria::expr()->gt('prize', 200)
    )
);

Of course, you can push logic when it is 1 condition, but when you have more conditions it is better to divide it into fragments, configure and pass it to the method:

$expr = Criteria::expr();

$criteria = Criteria::create();
$criteria->where($expr->gt('prize', 200));
$criteria->orderBy(['prize' => Criteria::DESC]);

$result = $purchases_repository->matching($criteria);
Gander
  • 1,854
  • 1
  • 23
  • 30
0

Copying the findBy query and modifying it to return your expected result is a good approach.

James Bissick
  • 105
  • 2
  • 7