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


Nobody is using Yaml mapping anymore

The are several reasons why is being withdrawn from Symfony (primarily from its 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 more significant and more complex. ORM Annotation seems to be a standard in nowadays Symfony projects, although if you see something like this

/**
 * @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!

Fortunately, you can still generate your entities from YAML files, which are a little bit simpler and easier to read.

Let’s assume you have to write entities for a model like this on the 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 happens.

You have these ORM mappings in your project, and you can generate entities from it with the 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 take 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 generated getters/setters the code, documented with annotations. Although to manage your relationships you are still using YAML.

Generate 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"

In annotations_entities directory there is a newly created fileCategoryAffiliate.php.

<?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;


}

Your annotations are there - ready to copy/paste. No need to worry about writing them by hand anymore!