Managing Doctrine 2 Entity Model mapping classes using YamlDriver in Zend Framework 2

This post continues my approach to integrate Doctrine 2 into Zend Framework 2 by additionally setting up YamlDriver to generate and manage Doctrine 2 Entity model mapping files.

First, we need to install the YAML dependency symfony/yaml:

//...
"require": {
        "php": ">=5.3.3",
        "zendframework/zendframework": "2.3.*",
        "zendframework/zend-developer-tools": "dev-master",
        "doctrine/doctrine-module": "dev-master",
        "doctrine/doctrine-orm-module": "dev-master",
        "symfony/yaml": "dev-master"
    }

And rebuild our project:

$ php composer.phar update
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing symfony/yaml (dev-master cee3067)
    Cloning cee3067d680232674c6505d91db9d3d635a9a8f4

Writing lock file
Generating autoload files

In case you forget the install the symfony/yaml module you will get an error like:

Fatal error: Class ‘Symfony\Component\Yaml\Yaml’ not found in \vendor\doctrine\orm\lib\Doctrine\ORM\Mapping\Driver\YamlDriver.php on line 712

Next, activate the YamlDriver for our skeleton Application module:

'doctrine' => array(
        'driver' => array(
            /* => replace with YamlDriver below
              'application_entities' => array(
              'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
              'cache' => 'array',
              'paths' => array(__DIR__ . '/../src/Application/Entity')
              ),
              'orm_default' => array(
              'drivers' => array(
              'Application\Entity' => 'application_entities'
              )
              ) */

            'ApplicationYamlDriver' => array(
                'class' => 'Doctrine\ORM\Mapping\Driver\YamlDriver',
                'cache' => 'array',
                'extension' => '.dcm.yml',
                'paths' => array(__DIR__ . '/yml')
            ),
            'orm_default' => array(
                'drivers' => array(
                    'Application\Entity' => 'ApplicationYamlDriver',
                )
            )
        )),

Make sure to create the folder src/Application/config/yml in which we will put our YAML model files. You can change this folder to whatever you like but I think config is quite a safe place for model declaration files.

Trying to refresh our application at this point will generate a ReflectionException since our models are currently not in-sync.

Thus, let’s create our User Entity YAML file Application.Entity.User.dcm.yml:

Application\Entity\User:
  type: entity
  table: user
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    name:
      type: string
      length: 50

Please note that you need to follow the naming convention of YAML files exactly:

MODULE.Entity.MODEL.dcm.yml

Now it’s time to generate our Entity class based on the YAML file:

$ vendor/bin/doctrine-module.bat orm:generate-entities module/Application/src/
Processing entity "Application\Entity\User"

Entity classes generated to "\module\Application\src"

You should now have a User.php class in src/Application/Entity:

<?php

namespace Application\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * User
 */
class User
{
    /**
     * @var integer
     */
    private $id;

    /**
     * @var string
     */
    private $name;


    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     * @return User
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string 
     */
    public function getName()
    {
        return $this->name;
    }
}

Let’s quickly check this newly generates Entity by calling it in our IndexController:

public function indexAction() {
        $objectManager = $this
                ->getServiceLocator()
                ->get('Doctrine\ORM\EntityManager');

        $user = new \Application\Entity\User();
        $user->setName('Some great YAML developer :)');

        $objectManager->persist($user);
        $objectManager->flush();

        echo 'Hello there ' . $user->getId();
    }

As you can see I’ve used setName instead of setFullName from Zend Framework 2 and Doctrine 2 ORM Integration. Thus, reloading your application will throw an Exception:

An exception occurred while executing ‘INSERT INTO user (name) VALUES (?)’ with params [“Some great YAML developer :)”]:

SQLSTATE[42S22]: Column not found: 1054 Unknown column ‘name’ in ‘field list’

Our old database still has a field fullName instead of name. Thus, we first need to sync our database table user with our model. Since we are in a development environment we could check with a validate-schema call:

$ vendor/bin/doctrine-module.bat orm:validate-schema
[Mapping]  OK - The mapping files are correct.
[Database] FAIL - The database schema is not in sync with the current mapping file.

As you can see our database needs to be updated with our local model Entity mapping changes from our YAML file. So, let’s update it:

$ vendor/bin/doctrine-module.bat orm:schema-tool:update
ATTENTION: This operation should not be executed in a production environment.
           Use the incremental update to detect changes during development and use
           the SQL DDL provided to manually update your database in production.

The Schema-Tool would execute "1" queries to update the database.
Please run the operation by passing one - or both - of the following options:
    orm:schema-tool:update --force to execute the command
    orm:schema-tool:update --dump-sql to dump the SQL statements to the screen

As you can see Doctrine warns you that you shouldn’t call the update command in a production environment as it could lead to possible data loss. So, it’s always better to let it calculate the incremental update statements first to let you double check:

$ vendor/bin/doctrine-module.bat orm:schema-tool:update --dump-sql
ALTER TABLE user ADD name VARCHAR(50) NOT NULL, DROP fullName;

This statement shows what we’ve already discovered before – that we need to replace the column fullName with name.

So, go ahead and run this SQL statement to update your database.

Since I’m lazy this is just a proof a concept setup I will simply force-update the database via Doctrine (do not do this in a production environment!!):

$ vendor/bin/doctrine-module.bat orm:schema-tool:update --force
Updating database schema...
Database schema updated successfully! "1" queries were executed

Finally, our database is in-sync with our local mapping files:

$ vendor/bin/doctrine-module.bat orm:validate-schema
[Mapping]  OK - The mapping files are correct.
[Database] OK - The database schema is in sync with the mapping files.

Now that we have a sync’ed Entity mapping class let’s try to refresh our application again:

Success! The DBAL Exception is gone and we are actively using our Entity model mapping class generated from our Yaml file.

At this point you might ask what happens to generated Entity mapping class files when your YAML files change. Well, when calling generate-entities on changed YAML files Doctrine will try to merge custom code with generated stubs of the getter/setter methods. Be sure to backup your generated mapping classes before calling generate-entities to avoid loss of your precious code as this merge will definitely not work at all times.

You may also like...

5 Responses

  1. Gregzombie says:

    Hello,

    I’m french, soory for my english.
    The latest zendframework application version not support this configuration to composer.json file. I’ve benn a error during installation, the module synfony/yaml: “dev-master require the php 5.5.9.
    So, I’m changed this. A new error appear : all other modules require php 5.3.23 exclusivly.

    Thinks for your help.

    • Hey,
      I just tested this setup successfully with the latest ZF version from dev-master using PHP 5.3 and 5.6. Thus, please make sure that you reset your composer.lock to re-init the setup and try to install/update your environment again. Cheers

  2. Alejandro says:

    Great post!

    I was having problems because Doctrine didn’t find the yml files. So, the issue was the naming convention. Now it works!

    Thank you!

  1. 06/04/2014

    […] Next to come: Using YamlDriver to generate Entity model mapping classes via YAML files. […]

  2. 04/01/2015

    […] Want to know how to generate Entity model mapping classes at ease based on YAML files? Then read on: Using YamlDriver to generate Entity model mapping classes via YAML files. […]

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.