From 03e07b00680274c9668bcbc353576489f5186b94 Mon Sep 17 00:00:00 2001 From: Wouter J Date: Sun, 13 Oct 2019 02:24:25 +0200 Subject: [PATCH] Promote DQL queries instead of QueryBuilder --- doctrine.rst | 75 +++++++++++++++++++--------------- doctrine/associations.rst | 19 +++++---- messenger/custom-transport.rst | 37 +++++++---------- security/user_provider.rst | 8 +++- 4 files changed, 74 insertions(+), 65 deletions(-) diff --git a/doctrine.rst b/doctrine.rst index 7a4558240c9..f2cb25fa8fe 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -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; @@ -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(); diff --git a/doctrine/associations.rst b/doctrine/associations.rst index 62eaec04955..00fb8ab061d 100644 --- a/doctrine/associations.rst +++ b/doctrine/associations.rst @@ -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 diff --git a/messenger/custom-transport.rst b/messenger/custom-transport.rst index f1341950aae..fff427b497f 100644 --- a/messenger/custom-transport.rst +++ b/messenger/custom-transport.rst @@ -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) { @@ -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(); } @@ -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(); } @@ -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)); } diff --git a/security/user_provider.rst b/security/user_provider.rst index e7919f389d9..676cd9fa84f 100644 --- a/security/user_provider.rst +++ b/security/user_provider.rst @@ -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();