@@ -290,6 +290,14 @@ about your database, and instead *only* think about your objects. Instead of set
290
290
the category's integer id onto ``Product ``, you set the entire ``Category `` *object *.
291
291
Doctrine takes care of the rest when saving.
292
292
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
+
293
301
Fetching Related Objects
294
302
------------------------
295
303
@@ -330,24 +338,21 @@ the category (i.e. it's "lazily loaded").
330
338
Because we mapped the optiona ``OneToMany `` side, you can also query in the other
331
339
direction::
332
340
333
- public function showProductsAction($categoryId )
341
+ public function showProductsAction($id )
334
342
{
335
343
$category = $this->getDoctrine()
336
344
->getRepository(Category::class)
337
- ->find($categoryId );
345
+ ->find($id );
338
346
339
347
$products = $category->getProducts();
340
348
341
349
// ...
342
350
}
343
351
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.
351
356
352
357
.. sidebar :: Relationships and Proxy Classes
353
358
@@ -357,7 +362,7 @@ to the given ``Category`` object via their ``category_id`` value.
357
362
358
363
$product = $this->getDoctrine()
359
364
->getRepository(Product::class)
360
- ->find($productId );
365
+ ->find($id );
361
366
362
367
$category = $product->getCategory();
363
368
@@ -371,8 +376,8 @@ to the given ``Category`` object via their ``category_id`` value.
371
376
actually need that data (e.g. until you call ``$category->getName() ``).
372
377
373
378
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.
376
381
377
382
In the next section, when you retrieve the product and category data
378
383
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.
381
386
Joining Related Records
382
387
-----------------------
383
388
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
385
390
(e.g. a ``Category ``) and one for the related object(s) (e.g. the ``Product ``
386
391
objects).
387
392
@@ -397,34 +402,129 @@ following method to the ``ProductRepository`` class::
397
402
// src/Repository/ProductRepository.php
398
403
public function findOneByIdJoinedToCategory($productId)
399
404
{
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();
412
414
}
413
415
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
+
414
419
Now, you can use this method in your controller to query for a ``Product ``
415
420
object and its related ``Category `` with just one query::
416
421
417
- public function showAction($productId )
422
+ public function showAction($id )
418
423
{
419
424
$product = $this->getDoctrine()
420
425
->getRepository(Product::class)
421
- ->findOneByIdJoinedToCategory($productId );
426
+ ->findOneByIdJoinedToCategory($id );
422
427
423
428
$category = $product->getCategory();
424
429
425
430
// ...
426
431
}
427
432
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
+
428
528
More Information on Associations
429
529
--------------------------------
430
530
@@ -437,7 +537,7 @@ Doctrine's `Association Mapping Documentation`_.
437
537
438
538
If you're using annotations, you'll need to prepend all annotations with
439
539
``@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.
442
541
443
542
.. _`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