Symfony 3 Doctrine - how to generate ManyToOne multiple @JoinColumn annotations?

Multiple Joins annotations ain't easy to write. You can generate them from yaml in easily!

Posted by Lukasz D. Tulikowski on February 4, 2017

 

Table of content


    Nobody is using Yaml mapping anymore

    The are several reasons why is being withdrawn from Symfony (basically from it’s component - Doctrine).

    • Rewriting the internals of the mapping component, both for performance and for type-safety/ease of usage by Doctrine developers.
    • Due to the internal mapping changes, mapping drivers would have required constant re-adapting.
    • The aim is for less mapping drivers to maintain.
    • Doctrine developers aim at unifying mapping drivers via defining PHP annotations in xml

    What’s more, you have to keep additional files what with your entities makes your project bigger and more complex. ORM Annotation seems to be a standard in nowadays symfony projects, although if you see something like this

    <?php
    ...
    /**
     * @var \AppBundle\Entity\Category
     *
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Category", inversedBy="category_affiliates")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="category_id", referencedColumnName="id")
     * })
     */
    private $category;
    
    /**
     * @var \AppBundle\Entity\Affiliate
     *
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Affiliate", inversedBy="category_affiliates")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="affiliate_id", referencedColumnName="id")
     * })
     */
    private $affiliate;
    

    You may get a little bit dizzy especially, on the beginning of your adventure with ORM.

    Generate entities using YAML!

    Fortunatelly you can still generete your entities from Yaml files, which are a little bit simpler and easier to read.

    Let’s assume you have to write entities for model like this on diagram below.

    symfony Jobeet diagram

    Here are Yaml files covers mentioned diagram.

    Category

    Ens\JobeetBundle\Entity\Category:
      type: entity
      table: category
      id:
        id:
          type: integer
          generator: { strategy: AUTO }
      fields:
        name:
          type: string
          length: 255
          unique: true
      oneToMany:
        jobs:
          targetEntity: Job
          mappedBy: category
        category_affiliates:
          targetEntity: CategoryAffiliate
          mappedBy: category
    

    Job

    # src/AppBundle/Resources/config/doctrine/Job.orm.yml
    Ens\JobeetBundle\Entity\Job:
      type: entity
      table: job
      id:
        id:
          type: integer
          generator: { strategy: AUTO }
      fields:
        type:
          type: string
          length: 255
          nullable: true
        company:
          type: string
          length: 255
        logo:
          type: string
          length: 255
          nullable: true
        url:
          type: string
          length: 255
          nullable: true
        position:
          type: string
          length: 255
        location:
          type: string
          length: 255
        description:
          type: text
        how_to_apply:
          type: text
        token:
          type: string
          length: 255
          unique: true
        is_public:
          type: boolean
          nullable: true
        is_activated:
          type: boolean
          nullable: true
        email:
          type: string
          length: 255
        expires_at:
          type: datetime
        created_at:
          type: datetime
        updated_at:
          type: datetime
          nullable: true
      manyToOne:
        category:
          targetEntity: Category
          inversedBy: jobs
          joinColumn:
            name: category_id
            referencedColumnName: id
      lifecycleCallbacks:
        prePersist: [ setCreatedAtValue ]
        preUpdate: [ setUpdatedAtValue ]
    

    Affiliate

    # src/AppBundle/Resources/config/doctrine/Affiliate.orm.yml
    Ens\JobeetBundle\Entity\Affiliate:
      type: entity
      table: affiliate
      id:
        id:
          type: integer
          generator: { strategy: AUTO }
      fields:
        url:
          type: string
          length: 255
        email:
          type: string
          length: 255
          unique: true
        token:
          type: string
          length: 255
        created_at:
          type: datetime
      oneToMany:
        category_affiliates:
          targetEntity: CategoryAffiliate
          mappedBy: affiliate
      lifecycleCallbacks:
        prePersist: [ setCreatedAtValue ]
    

    And finally… CategoryAffiliate

    
    # src/AppBundle/Resources/config/doctrine/CategoryAffiliate.orm.yml
    Ens\JobeetBundle\Entity\CategoryAffiliate:
      type: entity
      table: category_affiliate
      id:
        id:
          type: integer
          generator: { strategy: AUTO }  
      manyToOne:
        category:
          targetEntity: Category
          inversedBy: category_affiliates
          joinColumn:
            name: category_id
            referencedColumnName: id
        affiliate:
          targetEntity: Affiliate
          inversedBy: category_affiliates
          joinColumn:
            name: affiliate_id
            referencedColumnName: id
          
    
    

    I’ve selected part, where multiple @JoinColumn will happen. So far it’s simple.

    You have those ORM mappings in your project, and you can generate entities from it with single command

    $ bin/console doctrine:generate:entities AppBundle
    Generating entities for bundle "AppBundle"
      > backing up Job.php to Job.php~
      > generating AppBundle\Entity\Job
      > backing up Category.php to Category.php~
      > generating AppBundle\Entity\Category
      > backing up Affiliate.php to Affiliate.php~
      > generating AppBundle\Entity\Affiliate
      > backing up CategoryAffiliate.php to CategoryAffiliate.php~
      > generating AppBundle\Entity\CategoryAffiliate
    
    

    Let’s have have a look at CategoryAffiliate.php

    <?php
    
    namespace AppBundle\Entity;
    
    /**
     * CategoryAffiliate
     */
    class CategoryAffiliate
    {
        /**
         * @var integer
         */
        private $id;
    
        /**
         * @var \AppBundle\Entity\Category
         */
        private $category;
    
        /**
         * @var \AppBundle\Entity\Affiliate
         */
        private $affiliate;
    
    
        /**
         * Get id
         *
         * @return integer
         */
        public function getId()
        {
            return $this->id;
        }
    
        /**
         * Set category
         *
         * @param \AppBundle\Entity\Category $category
         *
         * @return CategoryAffiliate
         */
        public function setCategory(\AppBundle\Entity\Category $category = null)
        {
            $this->category = $category;
    
            return $this;
        }
    
        /**
         * Get category
         *
         * @return \AppBundle\Entity\Category
         */
        public function getCategory()
        {
            return $this->category;
        }
    
        /**
         * Set affiliate
         *
         * @param \AppBundle\Entity\Affiliate $affiliate
         *
         * @return CategoryAffiliate
         */
        public function setAffiliate(\AppBundle\Entity\Affiliate $affiliate = null)
        {
            $this->affiliate = $affiliate;
    
            return $this;
        }
    
        /**
         * Get affiliate
         *
         * @return \AppBundle\Entity\Affiliate
         */
        public function getAffiliate()
        {
            return $this->affiliate;
        }
    }
    

    Nice! You have setter/getters generated, and code is well documented with comments. But… You still use YAML to manage your relationships.

    Generete annotations

    Now time for a little magic. Run

    $ bin/console doctrine:mapping:convert annotation annotations_entities
    Processing entity "AppBundle\Entity\Job"
    Processing entity "AppBundle\Entity\Category"
    Processing entity "AppBundle\Entity\Affiliate"
    Processing entity "AppBundle\Entity\CategoryAffiliate"
    
    Exporting "annotation" mapping information to "/home/lukasz/App/annotations_entities"
    

    Now look into newly created CategoryAffiliate.php file in annotations_entities directory.

    <?php
    
    namespace AppBundle\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * CategoryAffiliate
     *
     * @ORM\Table(name="category_affiliate")
     * @ORM\Entity
     */
    class CategoryAffiliate
    {
        /**
         * @var integer
         *
         * @ORM\Column(name="id", type="integer")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="IDENTITY")
         */
        private $id;
    
        /**
         * @var \AppBundle\Entity\Category
         *
         * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Category", inversedBy="category_affiliates")
         * @ORM\JoinColumns({
         *   @ORM\JoinColumn(name="category_id", referencedColumnName="id")
         * })
        */
        private $category;
    
        /**
         * @var \AppBundle\Entity\Affiliate
         *
         * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Affiliate", inversedBy="category_affiliates")
         * @ORM\JoinColumns({
         *   @ORM\JoinColumn(name="affiliate_id", referencedColumnName="id")
         * })
         */
        private $affiliate;
    
    
    }
    

    Here are your annotations, ready to copy/paste. You don’t have to worry about writing them by hand anymore!