Monday, May 04, 2015

[Symfony] Database and Doctrine (2) - Entity Relationships

  
Entity Relationships/Associations



Creating the Category entity. Let Doctrine create the class for you.

$ php app/console doctrine:generate:entity \
   --entity="AppBundle:Category" \
   --fields="name:string(255)"

Relationship Mapping Metadata

To relate the Category and Product entities, start by creating a products property on the Category class.

# src/AppBundle/Resources/config/doctrine/Category.orm.yml
AppBundle\Entity\Category:
   type: entity
   # ...
   oneToMany:
       products:
           targetEntity: Product
           mappedBy: category
   # don't forget to init the collection in the Category::__construct() method
   # $this->products = new ArrayCollection();
   # of the entity
First, since a Category object will relate to many Product objects, a products array property is added to hold those Product objects. This isn't done because Doctrine needs it, but because it makes sense in the application for each Category to hold an array of Product objects.
// src/AppBundle/Entity/Category.php

use Doctrine\Common\Collections\ArrayCollection;

class Category
{
    // ...

    /**
    * @ORM\OneToMany(targetEntity="Product", mappedBy="category")
    */
    protected $products;

    public function __construct()
    {
       $this->products = new ArrayCollection();
    }
}

Next, since each Product class can relate to exactly one Category object, you'll want to add a $category property to the Product class:
// src/AppBundle/Entity/Product.php
class Product
{
    // ...

    /**
    * @ORM\ManyToOne(targetEntity="Category", inversedBy="products")
    * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
    */
    protected $category;
}

Finally, tell Doctrine to generate the missing getter and setter methods of the added new property in both the Category and Product classes:
$ php app/console doctrine:generate:entities AppBundle


Before you continue, be sure to tell Doctrine to add the new category table, and product.category_id column, and new foreign key:
$ php app/console doctrine:schema:update --force



Saving Related Entities

use AppBundle\Entity\Category;
use AppBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;

class DefaultController extends Controller
{
   public function createProductAction()
   {
       $category = new Category();
      //set category data
       
  $product = new Product();
       // Set product data   
  // relate this product to the category
       $product->setCategory($category);

       $em = $this->getDoctrine()->getManager();
       $em->persist($category);
       $em->persist($product);
       $em->flush();

       return new Response(
           'Created product id: '.$product->getId()
           .' and category id: '.$category->getId()
       );
   }
}
Now, a single row is added to both the category and product tables. The product.category_id column for the new product is set to whatever the id is of the new category. Doctrine manages the persistence of this relationship for you.

Fetching Related Objects

public function showAction($id)
{
    $product = $this->getDoctrine()
       ->getRepository('AppBundle:Product')
       ->find($id);

    $categoryName = $product->getCategory()->getName();
    // ...
}

When you call $product->getCategory()->getName(), Doctrine silently makes a second query to find the Category that's related to this Product. It prepares the $category object and returns it to you.
You can also query in the other direction:
public function showProductsAction($id)
{
   $category = $this->getDoctrine()
       ->getRepository('AppBundle:Category')
       ->find($id);

   $products = $category->getProducts();
   // ...
}

Joining Related Records

You can avoid the second query by issuing a join in the original query. Add the following method to the ProductRepository class:

// src/AppBundle/Entity/ProductRepository.php
public function findOneByIdJoinedToCategory($id)
{
   $query = $this->getEntityManager()
       ->createQuery(
           'SELECT p, c FROM AppBundle:Product p
           JOIN p.category c
           WHERE p.id = :id'
       )->setParameter('id', $id);

   try {
       return $query->getSingleResult();
   } catch (\Doctrine\ORM\NoResultException $e) {
       return null;
   }
}

Lifecycle Callbacks

Sometimes, you need to perform an action right before or after an entity is inserted, updated, or deleted. These types of actions are known as "lifecycle" callbacks.

# src/AppBundle/Resources/config/doctrine/Product.orm.yml
AppBundle\Entity\Product:
    type: entity
    # ...
    lifecycleCallbacks:
       prePersist: [setCreatedAtValue]

====================================
// src/AppBundle/Entity/Product.php
/**
* @ORM\PrePersist
*/
public function setCreatedAtValue()
{
    $this->createdAt = new \DateTime();
} 

Reference

Symfony Doctrine

No comments: