@@ -290,6 +290,14 @@ about your database, and instead *only* think about your objects. Instead of set
290290the category's integer id onto ``Product ``, you set the entire ``Category `` *object *.
291291Doctrine 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+
293301Fetching Related Objects
294302------------------------
295303
@@ -330,24 +338,21 @@ the category (i.e. it's "lazily loaded").
330338Because we mapped the optiona ``OneToMany `` side, you can also query in the other
331339direction::
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.
381386Joining 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 ``
386391objects).
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+
414419Now, you can use this method in your controller to query for a ``Product ``
415420object 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+
428528More 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
0 commit comments