ZF2 Skeleton Application with Doctrine and ctrllib

Building further upon the previous post, I’m gonna follow up with some more integration into ZF2. I’ve been building this class library as a ZF2 module. since it is only a class library, it does not contain any MVC elements. So to be able to work with it I’ve also copied the ZendSkeletonApplication, and added a couple of tools.

  • Composer : dependency management
  • Doctrine 2: ORM Layer (and database managerment via cli)
  • Phing: build scripting and database management
  • DbDeploy: database migration
  • ctrllib: my extensions library for ZF2 and Doctrine 2
  • ctrlAuth: a module based on ctrllib provinding MVC for authentication and authorization
  • Twitter Bootstrap: view helpers and layout

To install the SkeletonApplication just clone the github repo, and fire the ‘composer.phar install’ command.

for more setup options, follow the installation guide in the README file in the repo.
The application provides a default database setup you can manage with phing.

After installation, your application should look like this.

app landing page

ctrlSkeleton

creating your domain models

To create your domain you need 3 things for every entity: a class with a Doctrine mapping and a DomainService. The SkeletonApplication provides a default module ‘App’, with its doctrine mappings set to search for xml mappings in ‘modules/App/config/entities/’.

This post will show the classes and doctrine integration with phing.

As an example i’m going to create a Company and a Car model:

// module/App/src/App/Domain/Company.php
namespace App\Domain;

use Ctrl\Domain\PersistableModel;

class Company extends PersistableModel
{
    /**
     * @var string
     */
    protected $name;

    /**
     * @var Car[]|array
     */
    protected $cars;
}

// module/App/src/App/Domain/Car.php
namespace App\Domain;

use Ctrl\Domain\PersistableModel;

class Car extends PersistableModel
{
    /**
     * @var Company
     */
    protected $company;

    /**
     * @var string
     */
    protected $model;

    /**
     * @var int
     */
    protected $year;
}

module/App/config/entities/App.Domain.Company.dcm.xml

<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
 http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
 <entity name="App\Domain\Company" table="company">

<id name="id" type="integer" column="id">
 <generator strategy="AUTO"/>
 <sequence-generator sequence-name="autoinc" allocation-size="100" initial-value="1" />
 </id>

<field name="name" column="name" type="string" length="255" nullable="false" unique="true" />

<one-to-many field="cars" target-entity="App\Domain\Car" mapped-by="company">
 <cascade>
 <cascade-persist/>
 </cascade>
 </one-to-many>
 </entity>
</doctrine-mapping>

module/App/config/entities/App.Domain.Car.dcm.xml

<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
 http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
 <entity name="App\Domain\Car" table="car">

<id name="id" type="integer" column="id">
 <generator strategy="AUTO"/>
 <sequence-generator sequence-name="autoinc" allocation-size="100" initial-value="1" />
 </id>

<field name="model" column="model" type="string" length="255" nullable="false" unique="true" />
 <field name="year" column="year" type="integer" nullable="true" />

<many-to-one field="company" target-entity="App\Domain\Company" inversed-by="cars">
 <cascade>
 <cascade-persist/>
 </cascade>
 <join-column name="company_id" referenced-column-name="id" />
 </many-to-one>

</entity>
</doctrine-mapping>

update the database with phing

cd to the build directory and type ‘phing -l’ to view all available options.

First thing you can do is validate the schema, this will fail because we have 2 entities that do not have tables in the database.

execute ‘phing orm-validate’

$ phing orm-validate

CtrlBlog > orm-validate:

Mapping  OK - The mapping files are correct.
Database FAIL - The database schema is not in sync with the current mapping file.
Execution of target "orm-validate" failed for the following reason: /data/workspace/mySkeleton/build/build.xml:29:10: Task exited with code 2

BUILD FAILED

Total time: 0.4195 seconds

to solve this problem, we regenerate the create table files, now including the new tables we need.
This creates a new 001.sql file in the dbDeploy delta dir, so should only be used when no other deltas are there yet (for now).

execute ‘phing orm-sql-from-entities db-reload’

$ phing orm-sql-from-entities db-reload

CtrlBlog > orm-sql-from-entities:

CtrlBlog > db-drop:

CtrlBlog > db-create:

CtrlBlog > db-migrate:

CtrlBlog > db-load-sampledata:

CtrlBlog > db-reload:

BUILD FINISHED

Total time: 0.5898 seconds

Now we can validate again via phing, to check if we are good.

$ phing orm-validate

CtrlBlog > orm-validate:

Mapping  OK - The mapping files are correct.
Database OK - The database schema is in sync with the mapping files.

BUILD FINISHED

Total time: 0.4211 seconds

Because our models don’t know anything about their persistence on their own, we will manage them through DomainServices, in the next post.