Category: Zend Framework

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

    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.