Skip to content

[Doctrine] Promote DQL in favor of QueryBuilder #12469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 43 additions & 32 deletions doctrine.rst
Original file line number Diff line number Diff line change
Expand Up @@ -701,28 +701,30 @@ a new method for this to your repository::
}

/**
* @param $price
* @return Product[]
*/
public function findAllGreaterThanPrice($price): array
{
// automatically knows to select Products
// the "p" is an alias you'll use in the rest of the query
$qb = $this->createQueryBuilder('p')
->andWhere('p.price > :price')
->setParameter('price', $price)
->orderBy('p.price', 'ASC')
->getQuery();

return $qb->execute();

// to get just one result:
// $product = $qb->setMaxResults(1)->getOneOrNullResult();
$entityManager = $this->getEntityManager();

$query = $entityManager->createQuery(
'SELECT p
FROM App\Entity\Product p
WHERE p.price > :price
ORDER BY p.price ASC'
)->setParameter('price', $price);

// returns an array of Product objects
return $query->getResult();
}
}

This uses Doctrine's `Query Builder`_: a very powerful and user-friendly way to
write custom queries. Now, you can call this method on the repository::
The string passed to ``createQuery()`` might look like SQL, but it is
`Doctrine Query Language`_. This allows you to type queries using commonly
known query language, but referencing PHP objects instead (i.e. in the ``FROM``
statement).

Now, you can call this method on the repository::

// from inside a controller
$minPrice = 1000;
Expand All @@ -736,36 +738,45 @@ write custom queries. Now, you can call this method on the repository::
If you're in a :ref:`services-constructor-injection`, you can type-hint the
``ProductRepository`` class and inject it like normal.

For more details, see the `Query Builder`_ Documentation from Doctrine.
Querying with the Query Builder
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Querying with DQL or SQL
------------------------

In addition to the query builder, you can also query with `Doctrine Query Language`_::
Doctrine also provides a `Query Builder`_, an object-oriented way to write
queries. It is recommended to use this when queries and build dynamically (i.e.
based on PHP conditions)::

// src/Repository/ProductRepository.php
// ...

public function findAllGreaterThanPrice($price): array
// ...
public function findAllGreaterThanPrice($price, $includeUnavailableProducts = false): array
{
$entityManager = $this->getEntityManager();
// automatically knows to select Products
// the "p" is an alias you'll use in the rest of the query
$qb = $this->createQueryBuilder('p')
->where('p.price > :price')
->setParameter('price', $price)
->orderBy('p.price', 'ASC')

if (!$includeUnavailableProducts) {
$qb->andWhere('p.available = TRUE')
}

$query = $entityManager->createQuery(
'SELECT p
FROM App\Entity\Product p
WHERE p.price > :price
ORDER BY p.price ASC'
)->setParameter('price', $price);
$query = $qb->getQuery();

// returns an array of Product objects
return $query->execute();

// to get just one result:
// $product = $query->setMaxResults(1)->getOneOrNullResult();
}

Or directly with SQL if you need to::
Querying with SQL
~~~~~~~~~~~~~~~~~

In addition, you can query directly with SQL if you need to::

// src/Repository/ProductRepository.php
// ...

// ...
public function findAllGreaterThanPrice($price): array
{
$conn = $this->getEntityManager()->getConnection();
Expand Down
19 changes: 10 additions & 9 deletions doctrine/associations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -471,15 +471,16 @@ following method to the ``ProductRepository`` class::
// src/Repository/ProductRepository.php
public function findOneByIdJoinedToCategory($productId)
{
return $this->createQueryBuilder('p')
// p.category refers to the "category" property on product
->innerJoin('p.category', 'c')
// selects all the category data to avoid the query
->addSelect('c')
->andWhere('p.id = :id')
->setParameter('id', $productId)
->getQuery()
->getOneOrNullResult();
$entityManager = $this->getEntityManager();

$query = $entityManager->createQuery(
'SELECT p, c
FROM App\Entity\Product p
INNER JOIN p.category c
WHERE p.id = :id'
)->setParameter('id', $productId);

return $query->getOneOrNullResult();
}

This will *still* return an array of ``Product`` objects. But now, when you call
Expand Down
37 changes: 15 additions & 22 deletions messenger/custom-transport.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,13 @@ Here is a simplified example of a database transport::
public function get(): iterable
{
// Get a message from "my_queue"
$row = $this->db->createQueryBuilder()
->from('my_queue')
->where('delivered_at is null OR delivered_at < :redeliver_timeout')
->andWhere('handled = :false')
$row = $this->db->createQuery(
'SELECT *
FROM my_queue
WHERE (delivered_at IS NULL OR delivered_at < :redeliver_timeout')
AND handled = FALSE'
)
->setParameter('redeliver_timeout', new DateTimeImmutable('-5minutes'))
->setParameter('false', false)
->getOneOrNullResult();

if (null === $row) {
Expand All @@ -85,12 +86,7 @@ Here is a simplified example of a database transport::
}

// Mark the message as "handled"
$this->db->createQueryBuilder()
->update('my_queue')
->setValues([
'handled' => true
])
->where('id = :id')
$this->db->createQuery('UPDATE my_queue SET handled = TRUE WHERE id = :id')
->setParameter('id', $stamp->getId())
->execute();
}
Expand All @@ -103,9 +99,7 @@ Here is a simplified example of a database transport::
}

// Delete the message from the "my_queue" table
$this->db->createQueryBuilder()
->delete('my_queue')
->where('id = :id')
$this->db->createQuery('DELETE FROM my_queue WHERE id = :id')
->setParameter('id', $stamp->getId())
->execute();
}
Expand All @@ -116,14 +110,13 @@ Here is a simplified example of a database transport::
$uuid = Uuid::uuid4()->toString();

// Add a message to the "my_queue" table
$this->db->createQueryBuilder()
->insert('my_queue')
->values([
'id' => $uuid,
'envelope' => $encodedMessage['body'],
'delivered_at' => null,
'handled' => false,
]);
$this->db->createQuery(
'INSERT INTO my_queue (id, envelope, delivered_at, handled)
VALUES (:id, :envelope, NULL, FALSE)'
)
->setParameter('id', $uuid)
->setParameter('envelope', $encodedMessage['body'])
->execute();

return $envelope->with(new TransportMessageIdStamp($uuid));
}
Expand Down
8 changes: 6 additions & 2 deletions security/user_provider.rst
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,12 @@ interface only requires one method: ``loadUserByUsername($username)``::

public function loadUserByUsername($usernameOrEmail)
{
return $this->createQueryBuilder('u')
->where('u.username = :query OR u.email = :query')
return $this->createQuery(
'SELECT u
FROM App\Entity\User u
WHERE u.username = :query
OR u.email = :query'
)
->setParameter('query', $usernameOrEmail)
->getQuery()
->getOneOrNullResult();
Expand Down