First of all, I want to apologize for the length of this question; I didn't know how to properly ask my question without a lot of background. Please bear with me.
I'm converting a simple application that I use to hone my skills from my own custom database access schema to Doctrine. I chose Doctrine for a number of reasons, not the least of which is that I use it at my day job regularly. I also like how Doctrine is generally a pretty thin (appearing) layer that stays out of the way while still adding a lot of features.
I've converted the data access layer for the users table in my database to Doctrine.
It's very unremarkable (simply getters and setters), except for a few fine details:
I need to have a custom repository for some specific queries and
the User object has a default ArrayCollection instantiated in the constructor
namespace model\entities;
/**
* #Entity(repositoryClass="model\repositories\UserRepository")
* #Table(name="users")
*/
class User{
/* snip variables */
/**
* #OneToOne(targetEntity="Authentication", mappedBy="user", cascade="persist")
*/
private $authentication;
/**
* #OneToMany(targetEntity="Contact", mappedBy="user", cascade="persist")
*/
private $contacts;
public function __construct() {
$this->contacts = new \Doctrine\Common\Collections\ArrayCollection();
}
/* snip getters and setters */
}
In my old schema, I had two custom queries that selected a subset of the users table.
They are:
public function search( $term = null ){
if( !$term ){
$sql = "SELECT *
FROM
" . $this->tableName . "
ORDER BY
lname ASC,
fname ASC";
$res = $this->db->q($sql);
}
else{
$sql = "SELECT *
FROM
" . $this->tableName . "
WHERE
lname LIKE ?
OR fname LIKE ?
OR CONCAT(fname, ' ', lname) LIKE ?
ORDER BY
lname ASC,
fname ASC";
$values = array( '%' . $term . '%', '%' . $term . '%', '%' . $term . '%' );
$res = $this->db->qwv( $sql, $values );
}
return $this->wrap( $res );
}
and:
public function getAllWithRestrictions(){
$sql = "SELECT *
FROM
" . $this->tableName . "
WHERE
userid IN
(
SELECT
userid
FROM
" . $this->uiPre . "authentications
WHERE
resetPassword = 1
OR disabled = 1
)";
$res = $this->db->q( $sql );
return $this->wrap($res);
}
where $this->db is a thin PHP PDO wrapper and $this->wrap does magic with zero/single/multiple rows returned and converting them into data objects.
Now, I figured this would be very easy to convert to Doctrine. In the case of getAllWithRestrictions it's simply a ->where, ->orWhere set, right? I don't know anymore.
I found these Stackoverflow questions that I used to try to construct my queries, but I'm running into error after error, and I'm not sure how far down the rabbit hole I need to go:
SQL Multiple sorting and grouping
Order by multiple columns with Doctrine
Doctrine: Multiple (whereIn OR whereIn) query?
Doctrine - or where?
My custom repository currently looks like this, but I can't say it's even close to correct as I've been fiddling with it for a long time, and it's just a hodge-podge of what I thought might work:
<?php
namespace model\repositories;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr;
class UserRepository extends EntityRepository{
public function search( $term ){
if( !$term ){
return $this
->_em
->createQuery('SELECT u FROM model\entities\User u')
->addOrderBy( 'u.lname' )
->addOrderBy( 'u.fname' )
->getResult();
}
else{
$qb = $this->_em->createQueryBuilder();
$qb ->select(array('u'))
->from('model\entities\User', 'u')
->where( $qb->expr()->like( 'u.lname', '?1' ) )
->orWhere( $qb->expr()->like( 'u.fname', '?2' ) )
->orWhere( $qb->expr()->like( 'CONCAT(u.fname, \' \', u.lname)', '?3' ) )
->addOrderBy( 'u.lname' )
->addOrderBy( 'u.fname' )
->setParameters(
array(
1 => $term,
2 => $term,
3 => $term
)
);
$query = $qb->getQuery();
return $query->getResult();
}
}
public function getAllWithRestrictions(){
$qb = $this->_em->createQueryBuilder();
$qb ->select(array('u'))
->from('model\entities\User', 'u')
->add('where', $qb->expr()->orx(
$qb->expr()->eq('u.disabled', '1'),
$qb->expr()->eq('u.resetPassword', '1')
));
$query = $qb->getQuery();
return $query->getResult();
}
}
EDIT: I just realized that I'm doing the getAllWithRestrictions query on the wrong table (it should be authentications.) In any case, it's the search method causing my issues right now. However, I will also need to know how to do something like $qb->expr()->eq('u.Authentication.disabled = '1') and I have no idea how DQL really works.
The particular error I'm getting right now is
Fatal error: Uncaught exception 'Doctrine\ORM\Query\QueryException' with message 'SELECT u FROM model\entities\User u WHERE u.lname LIKE ?1 OR u.fname LIKE ?2 OR CONCAT(u.fname, ' ', u.lname) LIKE ?3 ORDER BY u.lname ASC, u.fname ASC'
followed by
Doctrine\ORM\Query\QueryException: [Syntax Error] line 0, col 99: Error: Expected Doctrine\ORM\Query\Lexer::T_CLOSE_PARENTHESIS, got ','
But I've had a slew of different issues depending on how I construct the DQL/Query Builder.
Again, I expected these two SQL queries to be simple to convert to Doctrine / DQL. I'd like to do this without resorting to raw SQL through Doctrine (as many of the accepted answers I linked suggest). This MUST be easy. Does anyone know how to construct elegant Doctrine queries?
According to the CONCAT function parser in doctrine, it only takes 2 parameters separated by a comma,
//Doctrine/ORM/Query/AST/Functions/ConcatFunction.php
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->firstStringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->secondStringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
So this
->orWhere( $qb->expr()->like( 'CONCAT(u.fname, \' \', u.lname)', '?3' ) )
should be
->orWhere( $qb->expr()->like( 'CONCAT(u.fname, u.lname)', '?3' ) )
This will not throw any errors, but may be not what you actually need. In that case, you will have to roll your own custom function for CONCAT :)
In addition to #Broncha's answer above (which helped solve the error by adding a nested CONCAT like 'CONCAT(u.fname, CONCAT(\' \', u.lname))'), the other function can be fixed by joining on the entity required, then adding WHERE clauses on the join like so:
$qb ->select(array('u'))
->from('model\entities\User', 'u')
->leftJoin('u.authentication', 'a')
->add('where', $qb->expr()->orx(
$qb->expr()->eq('a.disabled', '1'),
$qb->expr()->eq('a.resetPassword', '1')
));
Related
I'm trying to do this SQL query with Doctrine QueryBuilder:
SELECT * FROM events WHERE NOT id in (SELECT event_id FROM ues WHERE user_id = $userID)
The UserEventStatus has foreign keys from User and event, as well as an integer for status.
I now want to query all events that dont have an entry in UserEventStatus from an particular User.
My function for this in the EventRepository looks like this:
public function getUnReactedEvents(int $userID){
$expr = $this->getEntityManager()->getExpressionBuilder();
$originalQuery = $this->createQueryBuilder('e');
$subquery= $this->createQueryBuilder('b');
$originalQuery->where(
$expr->not(
$expr->in(
'e.id',
$subquery
->select('ues.user')
->from('App/Entity/UserEventStatus', "ues")
->where(
$expr->eq('ues.user', $userID)
)
)
)
);
return $originalQuery->getQuery()->getResult();
}
But i get an error that says:
Error: Method Doctrine\Common\Collections\ArrayCollection::__toString() must not throw an exception, caught ErrorException: Catchable Fatal Error: Object of class Doctrine\ORM\EntityManager could not be converted to string (500 Internal Server Error)
Can anyone help me or point me to right point in the docs? Cause i failed to find something that describes my problem.
And another thing is, that I don't know if its possible, but it would be nice. Can I somehow make direct Object requests? I mean not with the string App/Entity/UserEventStatus but with something like UserEventStatus::class or something.
Thanks for your help in advance. :)
EDIT: It has to be $originalQuery->getQuery()->getResult() of course.
If its like it was with $subquery instead i recive [Semantical Error] line I0, col 41 near 'App/Entity/UserEventStatus': Error: Class 'App' is not defined. (500 Internal Server Error)
Second EDIT:
$expr = $this->getEntityManager()->getExpressionBuilder();
$queryBuilder = $this->createQueryBuilder('e');
$subquery= $this->createQueryBuilder('b')
->select('ues.user')
->from('UserEventStatus', "ues")
->add('where', $expr->eq('ues.user', $userID));
$originalQueryExpression = $expr->not($expr->in('e.id', $subquery));
$queryBuilder->add('where', $originalQueryExpression);
return $queryBuilder->getQuery()->getResult();
Third EDIT: Thanks to #Dilek I made it work with a JOIN. This is the final Query:
$queryBuilder = $this->createQueryBuilder('e')
->leftJoin('App\Entity\UserEventStatus', 'ues', 'WITH', 'ues.user=:userID')
->setParameter('userID', $userID)
->where($expr->orX($expr->not(
$expr->eq('e.id','ues.event')
),
$expr->not($expr->eq('ues.user', $userID)))
);
return $queryBuilder->getQuery()->getResult();
Building AND WHERE into a Query
public function search($term)
{
return $this->createQueryBuilder('cat')
->andWhere('cat.name = :searchTerm')
->setParameter('searchTerm', $term)
->getQuery()
->execute();
}
simple is: ->where('cat.name = :searchTerm')
UPDATE :
I think you need to use where in
$qb->add('where', $qb->expr()->in('ues.user', $userID));
And WHERE Or WHERE
Below is the code excerpt I have
$column_name = "ipAddress";
$qb = EntityManagerContainer::get()->createQueryBuilder();
$qb->select('u')
->from(BlacklistedIps::class, 'u');
if($search_term)
{
$clause = $qb->expr()->like("u.".$column_name, "'%$search_term%'");
$qb->where($clause);
}
$query = $qb->getQuery();
$result = $query->getResult();
It works absolutely fine (although it's open to SQL injection but that's another story).
My problem with this is the need to have "'%$search_term%'". Without this extra set of single quotes the query fails
Uncaught exception 'Doctrine\ORM\Query\QueryException' with message
'SELECT u FROM Orm\Entity\BlacklistedIps u WHERE u.ipAddress LIKE
%123% ORDER BY u.reason desc' in ***
I am not entirely sure I am doing it the right way. Because if I do, then there is a bug (mssing feature?) in Doctrine2. When I do
$qb->expr()->like("u.".$column_name, "%$search_term%");
then I am ABSOLUTELY sure that I am dealing with a string. When integers or booleans or floats, etc are compared to each other different operators are used, but definitely not LIKE. LIKE is used ONLY when dealing with strings, so quoting the string in DQL is exactly the only possible ->like method use case.
Please tell me I am doing something wrong. I've been using Doctrine2 for couple of days only and feel fascinated by it. But don't like strings not being quoted automatically for me.
it looks like a problem of how you use querybuilder. You should do something like that :
$qb ->where($qb->expr()->orX($qb->expr()->like('u.'.$column_name, $qb->expr()->literal("%$searchTerm%"))))
or
$qb->where($qb->expr()->like("u.".$column_name, array("%$searchTerm%")));
also to avoid sql injection, a good practice is to not pass user input in any querybuilder methods, use setParameter with ? or : instead.
$qb->where('u.'.$column_name.' LIKE :searchTerm')
$qb->setParameter('searchTerm', '%'.$searchTerm.'%')
or something like :
$qb->expr()->like('u.'.$column_name, '?1')
$qb->getQuery()->setParameter(1, '%' . $searchTerm . '%');
Notice the following:
how I've broken the query up for increased security of your database.
my use of "andWhere" throughout the query being built
how I've assigned the value of the executed query to $result
public function findPending($id)
{
$qb = $this->createQueryBuilder('o')
->addSelect('s')
->leftJoin('MyApp\\Model\\Entity\\Shipment', 's')
->orderBy('o.date_sent', 'DESC');
// Order has been sent and was not cancelled
$qb
->andWhere($qb->expr()->andX(
$qb->expr()->eq('o.date_cancelled','0000-00-00 00:00:00'),
$qb->expr()->neq('o.date_sent','0000-00-00 00:00:00')
));
$qb
->andWhere($qb->expr()->orX(
// Order doesn't have a shipment
$qb->expr()->isNull('s.order'),
// OR Order has a shipment
$qb->expr()->orX(
// Shipment has not been sent
$qb->expr()->eq('s.date_sent','0000-00-00 00:00:00'),
// OR Shipment has been sent AND it was cancelled
$qb->expr()->andX(
$qb->expr()->neq('s.date_sent','0000-00-00 00:00:00'),
$qb->expr()->eq('s.date_cancelled','0000-00-00 00:00:00')
)
)
));
$qb
->setMaxResults(6);
$result = $qb->getQuery()
->getResult();
return $result;
}
To see the query you've created add this before "$result"
$qb->getQuery():
I'm building a function for filter some records based on four parameters: $codigo, $anno, $term and $comite_tecnico. This is what I build until now:
public function filtrarNorma($codigo = null, $anno = null, $term = null, $comite_tecnico = null)
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb
->select('n')
->from("AppBundle:Norma", "n");
if ($codigo != NULL) {
$qb->where($qb->expr()->like('n.numero', ':codigo'));
$qb->setParameter('codigo', '%' . $codigo . '%');
}
if ($anno != NULL) {
$qb->orWhere($qb->expr()->like('n.anno', ':anno'));
$qb->setParameter('anno', '%' . $anno . '%');
}
if ($term != NULL) {
$qb->orWhere($qb->expr()->like('n.nombre', ':term'));
$qb->setParameter('term', '%' . $term. '%');
}
if ($comite_tecnico != NULL) {
$qb->orWhere($qb->expr()->like('n.comite_tecnico', ':comite_tecnico'));
$qb->setParameter('comite_tecnico', '%' . $comite_tecnico . '%');
}
return $qb->getQuery()->getResult();
}
Any time I try to perform a query I get this error:
An exception occurred while executing 'SELECT n0_.numero AS numero0,
n0_.anno AS anno1, n0_.id AS id2, n0_.nombre AS nombre3, n0_.activo AS
activo4, n0_.comite_tecnico_id AS comite_tecnico_id5 FROM
nomencladores.norma n0_ WHERE n0_.numero LIKE ? OR n0_.anno LIKE ?'
with params ["34", 45]:
SQLSTATE[42883]: Undefined function: 7 ERROR: operator does not exist:
integer ~~ unknown LINE 1: ...dores.norma n0_ WHERE n0_.numero LIKE $1
OR n0_.anno LIKE $2 ^ HINT: No operator matches the given name and
argument type(s). You might need to add explicit type casts.
That's telling me that I need to cast some of those parameters before send it to the PgSQL DB and execute the query to get results but my question is, how I do that on Doctrine2 DQL? It's possible? Any workaround or trick or something else? I've found this documentation but don't know which function apply and also how, can any give me some help or advice around this?
Edit with new tests
After users suggestions I made some changes to my code and now it looks like:
public function filtrarNorma($codigo = null, $anno = null, $term = null, $comite_tecnico = null)
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb
->select('n')
->from("AppBundle:Norma", "n");
if ($codigo != NULL) {
$qb->where($qb->expr()->like('n.numero', ':codigo'));
$qb->setParameter('codigo', '%'.$codigo.'%', PDO::PARAM_STR);
}
if ($anno != NULL) {
$qb->orWhere($qb->expr()->like('n.anno', ':anno'));
$qb->setParameter('anno', $anno, PDO::PARAM_INT);
}
if ($term != NULL) {
$qb->orWhere($qb->expr()->like('n.nombre', ':term'));
$qb->setParameter('term', '%'.$term.'%', PDO::PARAM_STR);
}
if ($comite_tecnico != NULL) {
$qb->orWhere($qb->expr()->like('IDENTITY(n.comite_tecnico)', ':comite_tecnico'));
$qb->setParameter('comite_tecnico', '%'.$comite_tecnico.'%', PDO::PARAM_INT);
}
return $qb->getQuery()->getResult();
}
But once again, get the same error:
An exception occurred while executing 'SELECT n0_.numero AS numero0,
n0_.anno AS anno1, n0_.id AS id2, n0_.nombre AS nombre3, n0_.activo AS
activo4, n0_.comite_tecnico_id AS comite_tecnico_id5 FROM
nomencladores.norma n0_ WHERE n0_.numero LIKE ? OR n0_.anno LIKE ?'
with params ["%4%", "4"]:
SQLSTATE[42883]: Undefined function: 7 ERROR: operator does not exist:
integer ~~ unknown LINE 1: ...dores.norma n0_ WHERE n0_.numero LIKE $1
OR n0_.anno LIKE $2 ^ HINT: No operator matches the given name and
argument type(s). You might need to add explicit type casts.
And as you may notice in this case params are passed as should be: ["%4%", "4"] but why the error? Still not getting where it's
Another test
So, getting ride of Doctrine Query Builder and applying some Doctrine Query Language I moved the query from the code above to this one:
$em = $this->getEntityManager();
$query = $em->createQuery("SELECT n from AppBundle:Norma n WHERE n.numero LIKE '%:codigo%' OR n.anno LIKE '%:anno%' OR n.nombre LIKE '%:term%' OR IDENTITY(n.comite_tecnico) LIKE '%:comite_tecnico%'");
$query->setParameters(array(
'codigo' => $codigo,
'anno' => $anno,
'term' => $term,
'comite_tecnico' => $comite_tecnico
));
return $query->getResult();
But in this case I get this message:
Invalid parameter number: number of bound variables does not match
number of tokens
If the query is made by OR should be the four parameters required?
Your first try actually works for me all the time. You can convert your integers using strval()'.
'%' . strval($anno) . '%';
After a deep research I've found the solution to my problem and want to share with others too. I should said also thanks to #ErwinBrandstetter, #b.b3rn4rd for their time and support and to #Pradeep which finally give me the idea for research and finally get problem fixed and I did by enabling implicit casting support in PostgreSQL.
For enable implicit casts you must therefore execute the following commands in your PostgreSQL console when connected to the template1 database, so that any database created afterward will come with the required CASTs (if your database is already created, execute the commands in your database as well):
CREATE FUNCTION pg_catalog.text(integer) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(int4out($1));';
CREATE CAST (integer AS text) WITH FUNCTION pg_catalog.text(integer) AS IMPLICIT;
COMMENT ON FUNCTION pg_catalog.text(integer) IS 'convert integer to text';
CREATE FUNCTION pg_catalog.text(bigint) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(int8out($1));';
CREATE CAST (bigint AS text) WITH FUNCTION pg_catalog.text(bigint) AS IMPLICIT;
COMMENT ON FUNCTION pg_catalog.text(bigint) IS 'convert bigint to text';
That's all, after running that on the current DB I'm using and also on template1 for future ones and keeping conditions on my code as follow, all works fine and without any errors:
if ($codigo != null) {
$qb->where($qb->expr()->like('n.numero', ':codigo'));
$qb->setParameter('codigo', '%'.$codigo.'%', PDO::PARAM_STR);
}
if ($anno != null) {
$qb->orWhere($qb->expr()->like('n.anno', ':anno'));
$qb->setParameter('anno', '%'.$anno.'%', PDO::PARAM_STR);
}
if ($term != null) {
$qb->orWhere($qb->expr()->like('n.nombre', ':term'));
$qb->setParameter('term', '%'.$term.'%', PDO::PARAM_STR);
}
if ($comite_tecnico != null) {
$qb->orWhere($qb->expr()->like('IDENTITY(n.comite_tecnico)', ':comite_tecnico'));
$qb->setParameter('comite_tecnico', '%'.$comite_tecnico.'%', PDO::PARAM_STR);
}
Happy coding!!
I think in your last try the raw SQL string should look like this:
$query = $em->createQuery("SELECT n.*
FROM nomencladores.norma n
WHERE n.numero LIKE '%' || :codigo || '%' OR
cast(n.anno AS text) LIKE '%' || :anno || '%' OR
n.nombre LIKE '%' || :term || '%' OR
IDENTITY(n.comite_tecnico) LIKE '%' || :comite_tecnico || '%'");
Any other column here not text or varchar? Cast it, too.
Don't know the IDENTITY() function. A spillover from Doctrine, as well?
Still, I don't know much about Doctrine.
You're trying to use LIKE on an integer, which doesn't make sense.
Cast the integer to its text representation. This might work:
$qb->where($qb->expr()->like('CAST(n.numero AS text)', ':codigo'));
I have this query with a subquery:
$query = $this->getEntityManager()->createQueryBuilder();
$subquery = $query;
$subquery
->select('f.following')
->from('ApiBundle:Follow', 'f')
->where('f.follower = :follower_id')
->setParameter('follower_id', $id)
;
$query
->select('c')
->from('ApiBundle:Chef', 'c')
->where('c.id <> :id')
->setParameter('id', $id)
;
$query
->andWhere(
$query->expr()->notIn('c.id', $subquery->getDQL())
);
return $query->getQuery()->getResult();
And I get this error:
[Semantical Error] line 0, col 116 near 'f, ApiBundle:Chef': Error: 'f' is already defined.
I can't find the cause of the error, the alias f is defined only one time. Any suggestions?
This issue is about objects and references in PHP.
When you do $subquery = $query;, $query being an object, you simply have $subquery pointing to the same value.
A PHP reference is an alias, which allows two different variables to
write to the same value. As of PHP 5, an object variable doesn't
contain the object itself as value anymore. It only contains an object
identifier which allows object accessors to find the actual object.
When an object is [...] assigned to another
variable, the different variables are not aliases: they hold a copy of
the identifier, which points to the same object.
Reference: http://us1.php.net/manual/en/language.oop5.references.php
It means in your code that when you write this:
$subquery
->select('f.following')
->from('ApiBundle:Follow', 'f')
->where('f.follower = :follower_id')
->setParameter('follower_id', $id)
;
This is equivalent to:
$query
->select('f.following')
->from('ApiBundle:Follow', 'f')
->where('f.follower = :follower_id')
->setParameter('follower_id', $id)
;
So when at the end you call:
$query->andWhere(
$query->expr()->notIn('c.id', $subquery->getDQL())
);
You are using 2 times the same object pointed by 2 different variables ($query === $subquery).
To solve this issue, you can either use:
$query = $this->getEntityManager()->createQueryBuilder();
$subquery = $this->getEntityManager()->createQueryBuilder();
Or the clone keyword:
$query = $this->getEntityManager()->createQueryBuilder();
$subquery = clone $query;
I would like to share my solution which requires ORM mapping:
Following entities are mapped like this:
Event 1:M Participant
Participant class
/**
* #ORM\ManyToOne(targetEntity="KKB\TestBundle\Entity\Event", inversedBy="participants")
* #ORM\JoinColumn(name="event_id", referencedColumnName="id", nullable=false)
*/
private $event;
Event class
/**
* #ORM\OneToMany(targetEntity="KKB\TestBundle\Entity\Participant", mappedBy="event", cascade={"persist"})
*/
private $participants;
class EventRepository extends \Doctrine\ORM\EntityRepository
{
public function getEventList($userId)
{
$query = $this->createQueryBuilder('e');
$subquery = $this->createQueryBuilder('se');
$subquery
->leftJoin('se.participants', 'p')
->where('p.user = :userId')
;
return $query->where($query->expr()->notIn('e.id', $subquery->getDQL()))
->setParameter('userId', $userId)
;
}
}
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.
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.
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();
}
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.
$criteria = new \Doctrine\Common\Collections\Criteria();
$criteria->where($criteria->expr()->gt('id', 'id'))
->setMaxResults(1)
->orderBy(array("id" => $criteria::DESC));
$results = $articlesRepo->matching($criteria);
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
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);
Copying the findBy query and modifying it to return your expected result is a good approach.