Skip to content

Commit 2b0236f

Browse files
committed
updating the rest of the Doctrine docs
1 parent 68ac454 commit 2b0236f

13 files changed

+279
-407
lines changed

doctrine.rst

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ code:
2323

2424
.. code-block:: terminal
2525
26-
composer require orm maker
26+
composer require doctrine maker
2727
2828
Configuring the Database
2929
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -36,7 +36,7 @@ The database connection information is stored as an environment variable called
3636
# .env
3737
3838
# customize this line!
39-
DATABASE_URL="mysql://db_user:[email protected]:3306/db_name?charset=utf8mb4&serverVersion=5.7"
39+
DATABASE_URL="mysql://db_user:[email protected]:3306/db_name"
4040
4141
# to use sqlite:
4242
# DATABASE_URL="sqlite://%kernel.project_dir%/var/app.db"
@@ -48,6 +48,9 @@ database for you:
4848
4949
$ php bin/console doctrine:database:create
5050
51+
There are more optiosn in ``config/packages/doctrine.yaml`` that you can configure,
52+
including your ``server_version`` (e.g. 5.7 if you're using MySQL 5.7), which may
53+
affect how Doctrine functions.
5154

5255
.. tip::
5356

@@ -75,7 +78,6 @@ You now have a new ``src/Entity/Product.php`` file::
7578

7679
/**
7780
* @ORM\Entity(repositoryClass="App\Repository\ProductRepository")
78-
* @ORM\Table()
7981
*/
8082
class Product
8183
{
@@ -140,7 +142,7 @@ in the database. This is usually done with annotations:
140142
141143
.. code-block:: yaml
142144
143-
# src/Resources/config/doctrine/Product.orm.yml
145+
# config/doctrine/Product.orm.yml
144146
App\Entity\Product:
145147
type: entity
146148
id:
@@ -159,7 +161,7 @@ in the database. This is usually done with annotations:
159161
160162
.. code-block:: xml
161163
162-
<!-- src/Resources/config/doctrine/Product.orm.xml -->
164+
<!-- config/doctrine/Product.orm.xml -->
163165
<?xml version="1.0" encoding="UTF-8" ?>
164166
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
165167
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -177,14 +179,16 @@ in the database. This is usually done with annotations:
177179
178180
Doctrine supports a wide variety of different field types, each with their own options.
179181
To see a full list of types and options, see `Doctrine's Mapping Types documentation`_.
182+
If you want to use ``xml`` instead of annotations, you'll need to configure this in your
183+
``config/packages/doctrine.yaml`` file.
180184

181185
.. caution::
182186

183187
Be careful not to use reserved SQL keywords as your table or column names
184188
(e.g. ``GROUP`` or ``USER``). See Doctrine's `Reserved SQL keywords documentation`_
185189
for details on how to escape these. Or, configure the table name with
186-
``@ORM\Table(name="groups")`` or the column name with the ``name="group_name"``
187-
option.
190+
``@ORM\Table(name="groups")`` above the class or configure the column name with
191+
the ``name="group_name"`` option.
188192

189193
.. _doctrine-creating-the-database-tables-schema:
190194

@@ -423,7 +427,7 @@ be able to go to ``/product/1`` to see your new product::
423427

424428
// or render a template
425429
// in the template, print things with {{ product.name }}
426-
// $this->render('product/show.html.twig', ['product' => $product]);
430+
// return $this->render('product/show.html.twig', ['product' => $product]);
427431
}
428432

429433
Try it out!
@@ -472,7 +476,40 @@ the :ref:`doctrine-queries` section.
472476

473477
If the number of database queries is too high, the icon will turn yellow to
474478
indicate that something may not be correct. Click on the icon to open the
475-
Symfony Profiler and see the exact queries that were executed.
479+
Symfony Profiler and see the exact queries that were executed. If you don't
480+
see the web debug toolbar, try running ``composer require profiler`` to install
481+
it.
482+
483+
Automatically Fetching Objects (ParamConverter)
484+
-----------------------------------------------
485+
486+
In many cases, you can use the `SensioFrameworkExtraBundle`_ to do the query
487+
for you automatically! First, install the bundle in case you don't have it:
488+
489+
.. code-block:: terminal
490+
491+
$ composer require annotations
492+
493+
Now, simplify your controller::
494+
495+
// src/Controller/ProductController.php
496+
497+
use App\Entity\Product;
498+
// ...
499+
500+
/**
501+
* @Route("/product/{id}", name="product_show")
502+
*/
503+
public function showAction(Product $product)
504+
{
505+
// use the Product!
506+
// ...
507+
}
508+
509+
That's it! The bundle uses the ``{id}`` from the route to query for the ``Product``
510+
by the ``id`` column. If it's not found, a 404 page is generated.
511+
512+
There are many more options you can use. Read more about the `ParamConverter`_.
476513

477514
Updating an Object
478515
------------------
@@ -692,14 +729,11 @@ Learn more
692729
.. _`Query Builder`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html
693730
.. _`Doctrine Query Language`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html
694731
.. _`Mapping Types Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping
695-
.. _`Property Mapping`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping
696732
.. _`Reserved SQL keywords documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#quoting-reserved-words
697-
.. _`Creating Classes for the Database`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#creating-classes-for-the-database
698733
.. _`DoctrineMongoDBBundle`: https://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html
699-
.. _`migrations`: https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html
700734
.. _`DoctrineFixturesBundle`: https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
701-
.. _`FrameworkExtraBundle documentation`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
702-
.. _`newer utf8mb4 character set`: https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html
703735
.. _`Transactions and Concurrency`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/transactions-and-concurrency.html
704736
.. _`DoctrineMigrationsBundle`: https://github.com/doctrine/DoctrineMigrationsBundle
705737
.. _`NativeQuery`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/native-sql.html
738+
.. _`SensioFrameworkExtraBundle`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html
739+
.. _`ParamConverter`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html

doctrine/associations.rst

Lines changed: 129 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,14 @@ about your database, and instead *only* think about your objects. Instead of set
290290
the category's integer id onto ``Product``, you set the entire ``Category`` *object*.
291291
Doctrine takes care of the rest when saving.
292292

293+
.. sidebar:: Updating the Relationship from the Inverse Side
294+
295+
Could you also call ``$category->setProducts()`` to set the relationship? Actually,
296+
no! Earlier, you did *not* add a ``setProducts()`` method on ``Category``. That's
297+
on purpose: you can *only* set data on the *owning* side of the relationship. In
298+
other words, if you call ``$category->setProducts()`` only, that is *completely*
299+
ignored when saving. For more details, see: `associations-inverse-side`_.
300+
293301
Fetching Related Objects
294302
------------------------
295303

@@ -330,24 +338,21 @@ the category (i.e. it's "lazily loaded").
330338
Because we mapped the optiona ``OneToMany`` side, you can also query in the other
331339
direction::
332340

333-
public function showProductsAction($categoryId)
341+
public function showProductsAction($id)
334342
{
335343
$category = $this->getDoctrine()
336344
->getRepository(Category::class)
337-
->find($categoryId);
345+
->find($id);
338346

339347
$products = $category->getProducts();
340348

341349
// ...
342350
}
343351

344-
TODO TODO, STARTING HERE!!!!!!!!!!!!!!!!!!
345-
346-
In this case, the same things occur: you first query out for a single ``Category``
347-
object, and then Doctrine makes a second query to retrieve the related ``Product``
348-
objects, but only once/if you ask for them (i.e. when you call ``getProducts()``).
349-
The ``$products`` variable is an array of all ``Product`` objects that relate
350-
to the given ``Category`` object via their ``category_id`` value.
352+
In this case, the same things occur: you first query for a single ``Category``
353+
object. Then, only when (and if) you access the products, Doctrine makes a second
354+
query to retrieve the related ``Product`` objects. This extra query can be avoided
355+
by adding JOINs.
351356

352357
.. sidebar:: Relationships and Proxy Classes
353358

@@ -357,7 +362,7 @@ to the given ``Category`` object via their ``category_id`` value.
357362

358363
$product = $this->getDoctrine()
359364
->getRepository(Product::class)
360-
->find($productId);
365+
->find($id);
361366

362367
$category = $product->getCategory();
363368

@@ -371,8 +376,8 @@ to the given ``Category`` object via their ``category_id`` value.
371376
actually need that data (e.g. until you call ``$category->getName()``).
372377

373378
The proxy classes are generated by Doctrine and stored in the cache directory.
374-
And though you'll probably never even notice that your ``$category``
375-
object is actually a proxy object, it's important to keep it in mind.
379+
You'll probably never even notice that your ``$category`` object is actually
380+
a proxy object.
376381

377382
In the next section, when you retrieve the product and category data
378383
all at once (via a *join*), Doctrine will return the *true* ``Category``
@@ -381,7 +386,7 @@ to the given ``Category`` object via their ``category_id`` value.
381386
Joining Related Records
382387
-----------------------
383388

384-
In the above examples, two queries were made - one for the original object
389+
In the examples above, two queries were made - one for the original object
385390
(e.g. a ``Category``) and one for the related object(s) (e.g. the ``Product``
386391
objects).
387392

@@ -397,34 +402,129 @@ following method to the ``ProductRepository`` class::
397402
// src/Repository/ProductRepository.php
398403
public function findOneByIdJoinedToCategory($productId)
399404
{
400-
$query = $this->getEntityManager()
401-
->createQuery(
402-
'SELECT p, c FROM App:Product p
403-
JOIN p.category c
404-
WHERE p.id = :id'
405-
)->setParameter('id', $productId);
406-
407-
try {
408-
return $query->getSingleResult();
409-
} catch (\Doctrine\ORM\NoResultException $e) {
410-
return null;
411-
}
405+
return $this->createQueryBuilder('p')
406+
// p.category refers to the "category" property on product
407+
->innerJoin('p.category', 'c')
408+
// selects all the category data to avoid the query
409+
->addSelect('c')
410+
->andWhere('p.id = :id')
411+
->setParameter('id', $productId)
412+
->getQuery()
413+
->getOneOrNullResult();
412414
}
413415

416+
This will *still* return an array of ``Product`` objects. But now, when you call
417+
``$product->getCategory()`` and use that data, no second query is made.
418+
414419
Now, you can use this method in your controller to query for a ``Product``
415420
object and its related ``Category`` with just one query::
416421

417-
public function showAction($productId)
422+
public function showAction($id)
418423
{
419424
$product = $this->getDoctrine()
420425
->getRepository(Product::class)
421-
->findOneByIdJoinedToCategory($productId);
426+
->findOneByIdJoinedToCategory($id);
422427

423428
$category = $product->getCategory();
424429

425430
// ...
426431
}
427432

433+
.. _associations-inverse-side:
434+
435+
Setting Information from the Inverse Side
436+
-----------------------------------------
437+
438+
So far, you've updated the relationship by calling ``$product->setCategory($category)``.
439+
This is no accident: you *must* set the relationship on the *owning* side. The owning
440+
side is always where the ``ManyToOne`` mapping is set (for a ``ManyToMany`` relation,
441+
you can choose which side is the owning side).
442+
443+
Does this means it's not possible to call ``$category->setProducts()``? Actually,
444+
it *is* possible, by writing clever methods. First, instead of a ``setProducts()``
445+
method, create a ``addProduct()`` method::
446+
447+
// src/Entity/Category.php
448+
449+
// ...
450+
class Category
451+
{
452+
// ...
453+
454+
public function addProduct(Product $product)
455+
{
456+
if ($this->products->contains($product)) {
457+
return;
458+
}
459+
460+
$this->products[] = $product;
461+
// set the *owning* side!
462+
$product->setCategory($this);
463+
}
464+
}
465+
466+
That's it! The *key* is ``$product->setCategory($this)``, which sets the *owning*
467+
side. Now, when you save, the relationship *will* update in the database.
468+
469+
What about *removing* a ``Product`` from a ``Category``? Add a ``removeProduct()``
470+
method::
471+
472+
// src/Entity/Category.php
473+
474+
// ...
475+
class Category
476+
{
477+
// ...
478+
479+
public function removeProduct(Product $product)
480+
{
481+
$this->products->removeElement($product);
482+
// set the owning side to null
483+
$product->setCategory(null);
484+
}
485+
}
486+
487+
To make this work, you *now* need to allow ``null`` to be passed to ``Product::setCategory()``:
488+
489+
.. code-block:: diff
490+
491+
// src/Entity/Product.php
492+
493+
// ...
494+
class Product
495+
{
496+
// ...
497+
498+
- public function getCategory(): Category
499+
+ public function getCategory(): ?Category
500+
// ...
501+
502+
- public function setCategory(Category $category)
503+
+ public function setCategory(Category $category = null)
504+
{
505+
$this->category = $category;
506+
}
507+
}
508+
509+
And that's it! Now, if you call ``$category->removeProduct($product)``, the ``category_id``
510+
on that ``Product`` will be set to ``null`` in the database.
511+
512+
But, instead of setting the ``category_id`` to null, what if you want the ``Product``
513+
to be *deleted* if it becomes "orphaned" (i.e. without a ``Category``)? To choose
514+
that behavior, use the `orphanRemoval`_ option inside ``Category``::
515+
516+
// src/Entity/Category.php
517+
518+
// ...
519+
520+
/**
521+
* @ORM\OneToMany(targetEntity="App\Entity\Product", mappedBy="category", orphanRemoval=true)
522+
*/
523+
private $products;
524+
525+
Thanks to this, if the ``Product`` is removed from the ``Category``, it will be
526+
removed from the database entirely.
527+
428528
More Information on Associations
429529
--------------------------------
430530

@@ -437,7 +537,7 @@ Doctrine's `Association Mapping Documentation`_.
437537

438538
If you're using annotations, you'll need to prepend all annotations with
439539
``@ORM\`` (e.g. ``@ORM\OneToMany``), which is not reflected in Doctrine's
440-
documentation. You'll also need to include the ``use Doctrine\ORM\Mapping as ORM;``
441-
statement, which *imports* the ``ORM`` annotations prefix.
540+
documentation.
442541

443542
.. _`Association Mapping Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html
543+
.. _`orphanRemoval`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html#orphan-removal

doctrine/custom_dql_functions.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ In Symfony, you can register your custom DQL functions as follows:
1313

1414
.. code-block:: yaml
1515
16-
# app/config/config.yml
16+
# config/packages/doctrine.yaml
1717
doctrine:
1818
orm:
1919
# ...
@@ -28,7 +28,7 @@ In Symfony, you can register your custom DQL functions as follows:
2828
2929
.. code-block:: xml
3030
31-
<!-- app/config/config.xml -->
31+
<!-- config/packages/doctrine.xml -->
3232
<container xmlns="http://symfony.com/schema/dic/services"
3333
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3434
xmlns:doctrine="http://symfony.com/schema/dic/doctrine"
@@ -52,7 +52,7 @@ In Symfony, you can register your custom DQL functions as follows:
5252
5353
.. code-block:: php
5454
55-
// app/config/config.php
55+
// config/packages/doctrine.php
5656
use App\DQL\StringFunction;
5757
use App\DQL\SecondStringFunction;
5858
use App\DQL\NumericFunction;

0 commit comments

Comments
 (0)