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.