query-builder.rst 9.63 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
SQL Query Builder
=================

Doctrine 2.1 ships with a powerful query builder for the SQL language. This QueryBuilder object has methods
to add parts to an SQL statement. If you built the complete state you can execute it using the connection
it was generated from. The API is roughly the same as that of the DQL Query Builder.

You can access the QueryBuilder by calling ``Doctrine\DBAL\Connection#createQueryBuilder``:

.. code-block:: php

    <?php

    $conn = DriverManager::getConnection(array(/*..*/));
    $queryBuilder = $conn->createQueryBuilder();

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
Security: Safely preventing SQL Injection
-----------------------------------------

It is important to understand how the query builder works in terms of
preventing SQL injection. Because SQL allows expressions in almost
every clause and position the Doctrine QueryBuilder can only prevent
SQL injections for calls to the methods ``setFirstResult()`` and
``setMaxResults()``.

All other methods cannot distinguish between user- and developer input
and are therefore subject to the possibility of SQL injection.

To safely work with the QueryBuilder you should **NEVER** pass user
input to any of the methods of the QueryBuilder and use the placeholder
``?`` or ``:name`` syntax in combination with
``$queryBuilder->setParameter($placeholder, $value)`` instead:

.. code-block:: php

    <?php

    $queryBuilder
39 40 41
        ->select('id', 'name')
        ->from('users')
        ->where('email = ?')
42
        ->setParameter(0, $userInputEmail)
43 44
    ;

45 46
.. note::

47 48
    The numerical parameters in the QueryBuilder API start with the needle
    ``0``, not with ``1`` as in the PDO API.
49

50 51 52 53
Building a Query
----------------

The ``\Doctrine\DBAL\Query\QueryBuilder`` supports building ``SELECT``,
54 55
``INSERT``, ``UPDATE`` and ``DELETE`` queries. Which sort of query you
are building depends on the methods you are using.
56 57 58 59 60 61 62 63

For ``SELECT`` queries you start with invoking the ``select()`` method

.. code-block:: php

    <?php

    $queryBuilder
64 65
        ->select('id', 'name')
        ->from('users');
66

67 68 69
For ``INSERT``, ``UPDATE`` and ``DELETE`` queries you can pass the
table name into the ``insert($tableName)``, ``update($tableName)``
and ``delete($tableName)``:
70 71 72 73 74

.. code-block:: php

    <?php

75
    $queryBuilder
76
        ->insert('users')
77 78
    ;

79
    $queryBuilder
80
        ->update('users')
81 82 83
    ;

    $queryBuilder
84
        ->delete('users')
85 86 87 88 89
    ;

You can convert a query builder to its SQL string representation
by calling ``$queryBuilder->getSQL()`` or casting the object to string.

90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
DISTINCT-Clause
~~~~~~~~~~~~~~~

The ``SELECT`` statement can be specified with a ``DISTINCT`` clause:

.. code-block:: php

    <?php

    $queryBuilder
        ->select('name')
        ->distinct()
        ->from('users')
    ;

105 106 107
WHERE-Clause
~~~~~~~~~~~~

108 109
The ``SELECT``, ``UPDATE`` and ``DELETE`` types of queries allow where
clauses with the following API:
110 111 112 113 114 115

.. code-block:: php

    <?php

    $queryBuilder
116 117 118
        ->select('id', 'name')
        ->from('users')
        ->where('email = ?')
119 120 121 122 123 124
    ;

Calling ``where()`` overwrites the previous clause and you can prevent
this by combining expressions with ``andWhere()`` and ``orWhere()`` methods.
You can alternatively use expressions to generate the where clause.

125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
Table alias
~~~~~~~~~~~

The ``from()`` method takes an optional second parameter with which a table
alias can be specified.

.. code-block:: php

    <?php

    $queryBuilder
        ->select('u.id', 'u.name')
        ->from('users', 'u')
        ->where('u.email = ?')
    ;

141 142 143 144 145 146 147 148 149 150 151 152 153
GROUP BY and HAVING Clause
~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``SELECT`` statement can be specified with ``GROUP BY`` and ``HAVING`` clauses.
Using ``having()`` works exactly like using ``where()`` and there are
corresponding ``andHaving()`` and ``orHaving()`` methods to combine predicates.
For the ``GROUP BY`` you can use the methods ``groupBy()`` which replaces
previous expressions or ``addGroupBy()`` which adds to them:

.. code-block:: php

    <?php
    $queryBuilder
jeroendedauw's avatar
jeroendedauw committed
154 155 156
        ->select('DATE(last_login) as date', 'COUNT(id) AS users')
        ->from('users')
        ->groupBy('DATE(last_login)')
157 158 159 160 161 162
        ->having('users > 10')
    ;

Join Clauses
~~~~~~~~~~~~

Andrew M's avatar
Andrew M committed
163
For ``SELECT`` clauses you can generate different types of joins: ``INNER``,
164 165 166
``LEFT`` and ``RIGHT``. The ``RIGHT`` join is not portable across all platforms
(Sqlite for example does not support it).

Andrew M's avatar
Andrew M committed
167
A join always belongs to one part of the from clause. This is why you have to
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
specify the alias of the ``FROM`` part the join belongs to as the first
argument.

As a second and third argument you can then specify the name and alias of the
join-table and the fourth argument contains the ``ON`` clause.

.. code-block:: php

    <?php
    $queryBuilder
        ->select('u.id', 'u.name', 'p.number')
        ->from('users', 'u')
        ->innerJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id')

The method signature for ``join()``, ``innerJoin()``, ``leftJoin()`` and
``rightJoin()`` is the same. ``join()`` is a shorthand syntax for
``innerJoin()``.

Order-By Clause
~~~~~~~~~~~~~~~

The ``orderBy($sort, $order = null)`` method adds an expression to the ``ORDER
BY`` clause. Be aware that the optional ``$order`` parameter is not safe for
user input and accepts SQL expressions.

.. code-block:: php

    <?php
    $queryBuilder
jeroendedauw's avatar
jeroendedauw committed
197 198 199 200
        ->select('id', 'name')
        ->from('users')
        ->orderBy('username', 'ASC')
        ->addOrderBy('last_login', 'ASC NULLS FIRST')
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
    ;

Use the ``addOrderBy`` method to add instead of replace the ``orderBy`` clause.

Limit Clause
~~~~~~~~~~~~

Only a few database vendors have the ``LIMIT`` clause as known from MySQL,
but we support this functionality for all vendors using workarounds.
To use this functionality you have to call the methods ``setFirstResult($offset)``
to set the offset and ``setMaxResults($limit)`` to set the limit of results
returned.

.. code-block:: php

    <?php
    $queryBuilder
jeroendedauw's avatar
jeroendedauw committed
218 219
        ->select('id', 'name')
        ->from('users')
220 221 222
        ->setFirstResult(10)
        ->setMaxResults(20);

223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
VALUES Clause
~~~~~~~~~~

For the ``INSERT`` clause setting the values for columns to insert can be
done with the ``values()`` method on the query builder:

.. code-block:: php

    <?php

    $queryBuilder
        ->insert('users')
        ->values(
            array(
                'name' => '?',
                'password' => '?'
            )
        )
241 242
        ->setParameter(0, $username)
        ->setParameter(1, $password)
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
    ;
    // INSERT INTO users (name, password) VALUES (?, ?)

Each subsequent call to ``values()`` overwrites any previous set values.
Setting single values instead of all at once is also possible with the
``setValue()`` method:

.. code-block:: php

    <?php

    $queryBuilder
        ->insert('users')
        ->setValue('name', '?')
        ->setValue('password', '?')
258 259
        ->setParameter(0, $username)
        ->setParameter(1, $password)
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
    ;
    // INSERT INTO users (name, password) VALUES (?, ?)

Of course you can also use both methods in combination:

.. code-block:: php

    <?php

    $queryBuilder
        ->insert('users')
        ->values(
            array(
                'name' => '?'
            )
        )
276
        ->setParameter(0, $username)
277 278 279 280 281 282
    ;
    // INSERT INTO users (name) VALUES (?)

    if ($password) {
        $queryBuilder
            ->setValue('password', '?')
283
            ->setParameter(1, $password)
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
        ;
        // INSERT INTO users (name, password) VALUES (?, ?)
    }

Not setting any values at all will result in an empty insert statement:

.. code-block:: php

    <?php

    $queryBuilder
        ->insert('users')
    ;
    // INSERT INTO users () VALUES ()

299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
Set Clause
~~~~~~~~~~

For the ``UPDATE`` clause setting columns to new values is necessary
and can be done with the ``set()`` method on the query builder.
Be aware that the second argument allows expressions and is not safe for
user-input:

.. code-block:: php

    <?php

    $queryBuilder
        ->update('users', 'u')
        ->set('u.logins', 'u.logins + 1')
        ->set('u.last_login', '?')
315
        ->setParameter(0, $userInputLastLogin)
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
    ;

Building Expressions
--------------------

For more complex ``WHERE``, ``HAVING`` or other clauses you can use expressions
for building these query parts. You can invoke the expression API, by calling
``$queryBuilder->expr()`` and then invoking the helper method on it.

Most notably you can use expressions to build nested And-/Or statements:

.. code-block:: php

    <?php

    $queryBuilder
jeroendedauw's avatar
jeroendedauw committed
332 333
        ->select('id', 'name')
        ->from('users')
334
        ->where(
335
            $queryBuilder->expr()->and(
jeroendedauw's avatar
jeroendedauw committed
336 337
                $queryBuilder->expr()->eq('username', '?'),
                $queryBuilder->expr()->eq('email', '?')
338 339 340
            )
        );

341
The ``and()`` and ``or()`` methods accept an arbitrary amount
342 343
of arguments and can be nested in each other.

344
There is a bunch of methods to create comparisons and other SQL snippets
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
on the Expression object that you can see on the API documentation.

Binding Parameters to Placeholders
----------------------------------

It is often not necessary to know about the exact placeholder names
during the building of a query. You can use two helper methods
to bind a value to a placeholder and directly use that placeholder
in your query as a return value:

.. code-block:: php

    <?php

    $queryBuilder
jeroendedauw's avatar
jeroendedauw committed
360 361 362
        ->select('id', 'name')
        ->from('users')
        ->where('email = ' .  $queryBuilder->createNamedParameter($userInputEmail))
363
    ;
jeroendedauw's avatar
jeroendedauw committed
364
    // SELECT id, name FROM users WHERE email = :dcValue1
365 366

    $queryBuilder
jeroendedauw's avatar
jeroendedauw committed
367 368 369
        ->select('id', 'name')
        ->from('users')
        ->where('email = ' .  $queryBuilder->createPositionalParameter($userInputEmail))
370
    ;
jeroendedauw's avatar
jeroendedauw committed
371
    // SELECT id, name FROM users WHERE email = ?