From 77c906c6a56fda07ff2f992d3de8054018aaeed9 Mon Sep 17 00:00:00 2001 From: natechicago Date: Sun, 10 Apr 2016 19:13:15 -0500 Subject: [PATCH 1/7] Editing the Doctrine section to improve accuracy and readability. --- book/doctrine.rst | 251 +++++++++++++++++++++++++--------------------- 1 file changed, 136 insertions(+), 115 deletions(-) diff --git a/book/doctrine.rst b/book/doctrine.rst index 6287f49ecdb..aad1158c990 100644 --- a/book/doctrine.rst +++ b/book/doctrine.rst @@ -721,15 +721,15 @@ Doctrine Query Language (DQL). DQL is similar to SQL except that you should imagine that you're querying for one or more objects of an entity class (e.g. ``Product``) instead of querying for rows on a table (e.g. ``product``). -When querying in Doctrine, you have two options: writing pure Doctrine queries +When querying in Doctrine, you have two main options: writing pure DQL queries or using Doctrine's Query Builder. Querying for Objects with DQL ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Imagine that you want to query for products, but only return products that -cost more than ``19.99``, ordered from cheapest to most expensive. You can use -Doctrine's native SQL-like language called DQL to make a query for this:: +Imagine that you want to query for products that cost more than ``19.99``, +ordered from least to most expensive. You can use DQL, Doctrine's native +SQL-like language, to construct a query for this scenario:: $em = $this->getDoctrine()->getManager(); $query = $em->createQuery( @@ -737,15 +737,15 @@ Doctrine's native SQL-like language called DQL to make a query for this:: FROM AppBundle:Product p WHERE p.price > :price ORDER BY p.price ASC' - )->setParameter('price', '19.99'); + )->setParameter('price', 19.99); $products = $query->getResult(); If you're comfortable with SQL, then DQL should feel very natural. The biggest -difference is that you need to think in terms of "objects" instead of rows -in a database. For this reason, you select *from* the ``AppBundle:Product`` -*object* (an optional shortcut for ``AppBundle\Entity\Product``) and then -alias it as ``p``. +difference is that you need to think in terms of selecting PHP objects, +instead of rows in a database. For this reason, you select *from* the +``AppBundle:Product`` *class* (an optional shortcut for ``AppBundle\Entity\Product``) +and then alias it as ``p``. .. tip:: @@ -799,11 +799,11 @@ Custom Repository Classes ~~~~~~~~~~~~~~~~~~~~~~~~~ In the previous sections, you began constructing and using more complex queries -from inside a controller. In order to isolate, test and reuse these queries, -it's a good practice to create a custom repository class for your entity and -add methods with your query logic there. +from inside a controller. In order to isolate, reuse and test these queries, +it's a good practice to create a custom repository class for your entity. +Methods containing your query logic can then be stored in this class. -To do this, add the name of the repository class to your mapping definition: +To do this, add the repository class name to your entity's mapping definition: .. configuration-block:: @@ -847,16 +847,22 @@ To do this, add the name of the repository class to your mapping definition: -Doctrine can generate the repository class for you by running the same command -used earlier to generate the missing getter and setter methods: +Doctrine can generate empty repository classes for all the entities in your +application via the same command used earlier to generate the missing getter +and setter methods: .. code-block:: bash $ php app/console doctrine:generate:entities AppBundle -Next, add a new method - ``findAllOrderedByName()`` - to the newly generated -repository class. This method will query for all the ``Product`` entities, -ordered alphabetically. +.. tip:: + + If you opt to create the repository classes yourself, they must extend + ``Doctrine\ORM\EntityRepository``. + +Next, add a new method - ``findAllOrderedByName()`` - to the newly-generated +product repository class. This method will query for all the ``Product`` +entities, ordered alphabetically by name. .. code-block:: php @@ -898,11 +904,13 @@ You can use this new method just like the default finder methods of the reposito Entity Relationships/Associations --------------------------------- -Suppose that the products in your application all belong to exactly one "category". -In this case, you'll need a ``Category`` object and a way to relate a ``Product`` -object to a ``Category`` object. Start by creating the ``Category`` entity. -Since you know that you'll eventually need to persist the class through Doctrine, -you can let Doctrine create the class for you. +Suppose that each product in your application belongs to exactly one "category". +In this case, you'll need a ``Category`` class, and a way to relate a +``Product`` object to a ``Category`` object. + +Start by creating the ``Category`` entity. Since you know that you'll eventually +need to persist category objects through Doctrine, you can let Doctrine create +the class for you. .. code-block:: bash @@ -916,8 +924,81 @@ a ``name`` field and the associated getter and setter functions. Relationship Mapping Metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To relate the ``Category`` and ``Product`` entities, start by creating a -``products`` property on the ``Category`` class: +In our example, each category can be associated with *many* products, while +each product can be associated with only *one* category. This relationship +can be summarized as: *many* products to *one* category (or equivalently, +*one* category to *many* products). + +From the perspective of the ``Product`` entity, this is a many-to-one relationship. +From the perspective of the ``Category`` entity, this is a one-to-many relationship. +This is important, because the relative nature of the relationship determines +which mapping metadata to use. It also determines which class *must* hold +a reference to the other class. + +To relate the ``Product`` and ``Category`` entities, simply create a ``category`` +property on the ``Product`` class, annotated as follows: + +.. configuration-block:: + + .. code-block:: php-annotations + + // src/AppBundle/Entity/Product.php + + // ... + class Product + { + // ... + + /** + * @ORM\ManyToOne(targetEntity="Category", inversedBy="products") + * @ORM\JoinColumn(name="category_id", referencedColumnName="id") + */ + private $category; + } + + .. code-block:: yaml + + # src/AppBundle/Resources/config/doctrine/Product.orm.yml + AppBundle\Entity\Product: + type: entity + # ... + manyToOne: + category: + targetEntity: Category + inversedBy: products + joinColumn: + name: category_id + referencedColumnName: id + + .. code-block:: xml + + + + + + + + + + + + + + +This mapping is critical, as it tells Doctrine to use the ``category_id`` +column on the ``products`` table to relate each record in that table with +a record in the ``category`` table. + +Next, since a single ``Category`` object will relate to many ``Product`` +objects, a ``products`` property can be added to the ``Category`` class +to hold those associated objects. .. configuration-block:: @@ -979,126 +1060,66 @@ To relate the ``Category`` and ``Product`` entities, start by creating a -First, since a ``Category`` object will relate to many ``Product`` objects, -a ``products`` array property is added to hold those ``Product`` objects. -Again, this isn't done because Doctrine needs it, but instead because it -makes sense in the application for each ``Category`` to hold an array of -``Product`` objects. +Doctrine does not *require* that the "one" side of a one-to-many relationship +hold a collection of its "many" related entities. But in the context of our +application, it makes sense for each ``Category`` object to hold an +array of ``Product`` objects. However, if we had decided against adding +a ``$products`` property to the ``Category`` class, the ``inversedBy`` +metadata on the Product class would not have been necessary. .. note:: - The code in the ``__construct()`` method is important because Doctrine - requires the ``$products`` property to be an ``ArrayCollection`` object. + The code in the constructor is important because the ``$products`` property + must be an ``ArrayCollection`` object, rather than a traditional ``array``. This object looks and acts almost *exactly* like an array, but has some added flexibility. If this makes you uncomfortable, don't worry. Just imagine that it's an ``array`` and you'll be in good shape. .. tip:: - The targetEntity value in the decorator used above can reference any entity + The targetEntity value in the metadata used above can reference any entity with a valid namespace, not just entities defined in the same namespace. To relate to an entity defined in a different class or bundle, enter a full namespace as the targetEntity. -Next, since each ``Product`` class can relate to exactly one ``Category`` -object, you'll want to add a ``$category`` property to the ``Product`` class: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/AppBundle/Entity/Product.php - - // ... - class Product - { - // ... - - /** - * @ORM\ManyToOne(targetEntity="Category", inversedBy="products") - * @ORM\JoinColumn(name="category_id", referencedColumnName="id") - */ - private $category; - } - - .. code-block:: yaml - - # src/AppBundle/Resources/config/doctrine/Product.orm.yml - AppBundle\Entity\Product: - type: entity - # ... - manyToOne: - category: - targetEntity: Category - inversedBy: products - joinColumn: - name: category_id - referencedColumnName: id - - .. code-block:: xml - - - - - - - - - - - - - - -Finally, now that you've added a new property to both the ``Category`` and -``Product`` classes, tell Doctrine to generate the missing getter and setter -methods for you: +Now that you've added new properties to both the ``Product`` and ``Category`` +classes, tell Doctrine to generate the missing getter and setter methods for you: .. code-block:: bash $ php app/console doctrine:generate:entities AppBundle -Ignore the Doctrine metadata for a moment. You now have two classes - ``Category`` -and ``Product`` with a natural one-to-many relationship. The ``Category`` -class holds an array of ``Product`` objects and the ``Product`` object can -hold one ``Category`` object. In other words - you've built your classes -in a way that makes sense for your needs. The fact that the data needs to -be persisted to a database is always secondary. - -Now, look at the metadata above the ``$category`` property on the ``Product`` -class. The information here tells Doctrine that the related class is ``Category`` -and that it should store the ``id`` of the category record on a ``category_id`` -field that lives on the ``product`` table. In other words, the related ``Category`` -object will be stored on the ``$category`` property, but behind the scenes, -Doctrine will persist this relationship by storing the category's id value -on a ``category_id`` column of the ``product`` table. +Ignore the Doctrine metadata for a moment. You now have two classes - ``Product`` +and ``Category``, with a natural many-to-one relationship. The ``Product`` +class holds a *single* ``Category`` object, and the ``Category`` class holds +a *collection* of ``Product`` objects. In other words, you've built your classes +in a way that makes sense for your application. The fact that the data needs +to be persisted to a database is always secondary. + +Now, review the metadata that was added above the ``Product`` entity's +``$category`` property. It tells Doctrine that the related class is ``Category``, +and that the ``id`` of the related category record should be stored in a +``category_id`` field on the ``product`` table. + +In other words, the related ``Category`` object will be stored in the +``$category`` property, but behind the scenes, Doctrine will persist this +relationship by storing the category's id in the ``category_id`` column +of the ``product`` table. .. image:: /images/book/doctrine_image_2.png :align: center -The metadata above the ``$products`` property of the ``Category`` object -is less important, and simply tells Doctrine to look at the ``Product.category`` +The metadata above the ``Category`` entity's ``$products`` property is less +complicated. It simply tells Doctrine to look at the ``Product.category`` property to figure out how the relationship is mapped. Before you continue, be sure to tell Doctrine to add the new ``category`` -table, and ``product.category_id`` column, and new foreign key: +table, the new ``product.category_id`` column, and the new foreign key: .. code-block:: bash $ php app/console doctrine:schema:update --force -.. note:: - - This command should only be used during development. For a more robust - method of systematically updating your production database, read about - `migrations`_. Saving Related Entities ~~~~~~~~~~~~~~~~~~~~~~~ From 447dec4d9fdd356491a7dd972ddaf981e44b09e9 Mon Sep 17 00:00:00 2001 From: natechicago Date: Mon, 11 Apr 2016 08:44:33 -0500 Subject: [PATCH 2/7] Editing the Doctrine section to improve accuracy and readability. Changes from code review. --- book/doctrine.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/book/doctrine.rst b/book/doctrine.rst index aad1158c990..340af9fe39a 100644 --- a/book/doctrine.rst +++ b/book/doctrine.rst @@ -744,8 +744,8 @@ SQL-like language, to construct a query for this scenario:: If you're comfortable with SQL, then DQL should feel very natural. The biggest difference is that you need to think in terms of selecting PHP objects, instead of rows in a database. For this reason, you select *from* the -``AppBundle:Product`` *class* (an optional shortcut for ``AppBundle\Entity\Product``) -and then alias it as ``p``. +``AppBundle:Product`` *entity* (an optional shortcut for the +``AppBundle\Entity\Product`` class) and then alias it as ``p``. .. tip:: @@ -904,7 +904,7 @@ You can use this new method just like the default finder methods of the reposito Entity Relationships/Associations --------------------------------- -Suppose that each product in your application belongs to exactly one "category". +Suppose that each product in your application belongs to exactly one category. In this case, you'll need a ``Category`` class, and a way to relate a ``Product`` object to a ``Category`` object. From d4d0e02639313d50d663ef6d49b07f3260b55008 Mon Sep 17 00:00:00 2001 From: natechicago Date: Mon, 11 Apr 2016 08:57:01 -0500 Subject: [PATCH 3/7] Editing the Doctrine section to improve accuracy and readability. --- book/doctrine.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/doctrine.rst b/book/doctrine.rst index 340af9fe39a..a8a14f01a94 100644 --- a/book/doctrine.rst +++ b/book/doctrine.rst @@ -1064,8 +1064,8 @@ Doctrine does not *require* that the "one" side of a one-to-many relationship hold a collection of its "many" related entities. But in the context of our application, it makes sense for each ``Category`` object to hold an array of ``Product`` objects. However, if we had decided against adding -a ``$products`` property to the ``Category`` class, the ``inversedBy`` -metadata on the Product class would not have been necessary. +a ``$products`` property to the ``Category`` class, then the ``Product`` +entity's ``inversedBy`` metadata would not have been necessary. .. note:: From 78b2fc511c4ff3a1057aab6beaf5d4f7b1fc6e27 Mon Sep 17 00:00:00 2001 From: natechicago Date: Sat, 16 Apr 2016 19:18:06 -0500 Subject: [PATCH 4/7] Editing the Doctrine section to improve accuracy and readability. Changes from code review. --- book/doctrine.rst | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/book/doctrine.rst b/book/doctrine.rst index a8a14f01a94..9244249a452 100644 --- a/book/doctrine.rst +++ b/book/doctrine.rst @@ -1062,18 +1062,20 @@ to hold those associated objects. Doctrine does not *require* that the "one" side of a one-to-many relationship hold a collection of its "many" related entities. But in the context of our -application, it makes sense for each ``Category`` object to hold an -array of ``Product`` objects. However, if we had decided against adding -a ``$products`` property to the ``Category`` class, then the ``Product`` -entity's ``inversedBy`` metadata would not have been necessary. +application, it makes sense for each ``Category`` object to hold a collection +of ``Product`` objects. However, if we had decided against adding a ``$products`` +property to the ``Category`` class, then the ``Product`` entity's ``inversedBy`` +metadata would have to be omitted. .. note:: - The code in the constructor is important because the ``$products`` property - must be an ``ArrayCollection`` object, rather than a traditional ``array``. - This object looks and acts almost *exactly* like an array, but has some - added flexibility. If this makes you uncomfortable, don't worry. Just - imagine that it's an ``array`` and you'll be in good shape. + The code in the constructor is important. Rather than being instantiated + as a traditional ``array``, the ``$products`` property must be of a type + that implements Doctrine's ``Collection`` interface. In this case, an + ``ArrayCollection`` object is used. This object looks and acts almost + *exactly* like an array, but has some added flexibility. If this makes + you uncomfortable, don't worry. Just imagine that it's an ``array`` + and you'll be in good shape. .. tip:: From 2bc5099b860d2219d6f9306747c387aa3cd8651b Mon Sep 17 00:00:00 2001 From: natechicago Date: Mon, 25 Apr 2016 08:14:28 -0500 Subject: [PATCH 5/7] Editing the Doctrine section to improve accuracy and readability. Changes from code review. --- book/doctrine.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/doctrine.rst b/book/doctrine.rst index 9244249a452..6e84a7aa942 100644 --- a/book/doctrine.rst +++ b/book/doctrine.rst @@ -1060,7 +1060,7 @@ to hold those associated objects. -Doctrine does not *require* that the "one" side of a one-to-many relationship +Doctrine does not *require* the "one" side of a one-to-many relationship to hold a collection of its "many" related entities. But in the context of our application, it makes sense for each ``Category`` object to hold a collection of ``Product`` objects. However, if we had decided against adding a ``$products`` From f4e192439722dc9dda0b717bd674e73ebe5a19dc Mon Sep 17 00:00:00 2001 From: natechicago Date: Mon, 25 Apr 2016 08:27:27 -0500 Subject: [PATCH 6/7] Editing the Doctrine section to improve accuracy and readability. Changes from code review. --- book/doctrine.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/doctrine.rst b/book/doctrine.rst index 6e84a7aa942..bb85cd352ad 100644 --- a/book/doctrine.rst +++ b/book/doctrine.rst @@ -924,7 +924,7 @@ a ``name`` field and the associated getter and setter functions. Relationship Mapping Metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In our example, each category can be associated with *many* products, while +In this example, each category can be associated with *many* products, while each product can be associated with only *one* category. This relationship can be summarized as: *many* products to *one* category (or equivalently, *one* category to *many* products). From c5698a5614838f33703bef2759cc9d402474bf1a Mon Sep 17 00:00:00 2001 From: natechicago Date: Sat, 30 Apr 2016 19:55:09 -0500 Subject: [PATCH 7/7] Editing the Doctrine section to improve accuracy and readability. Changes from code review. --- book/doctrine.rst | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/book/doctrine.rst b/book/doctrine.rst index bb85cd352ad..e03064a446d 100644 --- a/book/doctrine.rst +++ b/book/doctrine.rst @@ -861,7 +861,7 @@ and setter methods: ``Doctrine\ORM\EntityRepository``. Next, add a new method - ``findAllOrderedByName()`` - to the newly-generated -product repository class. This method will query for all the ``Product`` +``ProductRepository`` class. This method will query for all the ``Product`` entities, ordered alphabetically by name. .. code-block:: php @@ -992,8 +992,8 @@ property on the ``Product`` class, annotated as follows: -This mapping is critical, as it tells Doctrine to use the ``category_id`` -column on the ``products`` table to relate each record in that table with +This many-to-one mapping is critical. It tells Doctrine to use the ``category_id`` +column on the ``product`` table to relate each record in that table with a record in the ``category`` table. Next, since a single ``Category`` object will relate to many ``Product`` @@ -1060,12 +1060,11 @@ to hold those associated objects. -Doctrine does not *require* the "one" side of a one-to-many relationship to -hold a collection of its "many" related entities. But in the context of our -application, it makes sense for each ``Category`` object to hold a collection -of ``Product`` objects. However, if we had decided against adding a ``$products`` -property to the ``Category`` class, then the ``Product`` entity's ``inversedBy`` -metadata would have to be omitted. +While the many-to-one mapping shown earlier was mandatory, this one-to-many +mapping is optional. It is included here to help demonstrate Doctrine's range +of relationship management capabailties. Plus, in the context of this application, +it will likely be convenient for each ``Category`` object to automatically +own a collection of its related ``Product`` objects. .. note:: @@ -1098,10 +1097,10 @@ a *collection* of ``Product`` objects. In other words, you've built your classes in a way that makes sense for your application. The fact that the data needs to be persisted to a database is always secondary. -Now, review the metadata that was added above the ``Product`` entity's -``$category`` property. It tells Doctrine that the related class is ``Category``, -and that the ``id`` of the related category record should be stored in a -``category_id`` field on the ``product`` table. +Now, review the metadata above the ``Product`` entity's ``$category`` property. +It tells Doctrine that the related class is ``Category``, and that the ``id`` +of the related category record should be stored in a ``category_id`` field +on the ``product`` table. In other words, the related ``Category`` object will be stored in the ``$category`` property, but behind the scenes, Doctrine will persist this