Migrating to ZF2: Integrating Composer and DoctrineORMModule

Due to the vast nuances of Zend Framework 2, migrating an application from Zend Framework 1 can be very tedious.  To make this process a little less painful, there is a way to slowly implement modules from ZF2 without making the application unusable. The methodology illustrated, which implements DoctrineModule and DoctrineORMModule into a Zend Framework 1 project, can be applied to a variety of ZF2 modules, making it an invaluable technique in the migration process.

Create the ZF2 project structure

  • /config
  • /config/autoload
  • /data
  • /module
  • /module/Application
  • /module/Application/src
  • /module/Application/src/Application
  • /public
  • /vendor

Install Zend Framework 2 and Doctrine ORM using Composer

At the very least, the composer.json file must include the following.

{
    "require" : {
        "zendframework/zendframework" : "2.2.*",
        "doctrine/orm" : "2.*",
        "doctrine/doctrine-orm-module": "0.*"
    }
}

After updating the Composer configuration file, run Composer.

# Update Composer to latest version
php composer.phar self-update

# Install Composer packages
php composer.phar install

Add init_autoloader.php to application root directory

Integrating the Composer and ZF2 autoloading scheme can be a bit tricky if you rely on antiquated Zend Framework 1 components. To get around the peculiarities, use the classmap generator provided by Zend.

The init_autoloader.php file listed below is identical to the file provided in the ZendSkeletonApplication repository with a few exceptions. For one, the ZF2 autoloader is specified and configured making it simpler to use and understand. Secondly, several class maps are implemented as part of the default autoloading scheme. I recommend generating separate class map files for the application, library (if used), and ZF2 module directories. The class map file in the ZF2 module directory will be replaced with module-specific autoloading once the migration is complete.

// Composer autoloading
if (file_exists('vendor/autoload.php')) {
    $loader = include 'vendor/autoload.php';
}

ZendLoaderAutoloaderFactory::factory(array(
    'ZendLoaderStandardAutoloader' => array(
        'autoregister_zf' => true,
    ),
    'ZendLoaderClassMapAutoloader' => array(
        __DIR__ . '/application/autoload_classmap.php',
        __DIR__ . '/module/autoload_classmap.php',
    ),
));

if (!class_exists('ZendLoaderAutoloaderFactory')) {
    throw new RuntimeException('Unable to load ZF2. Run `php composer.phar install` or define a ZF2_PATH environment variable.');
}

Update ZF1′s index.php for Composer compatibility

There are two changes that will help bring your index file more in line with that of Zend Framework 2.

Place this line at the top of index.php.

NOTE: If relative paths are used in other parts of the application, it is likely that they will need to be updated.

// All paths relative to application root
chdir(dirname(__DIR__));

// Decline static file requests back to the PHP built-in webserver
if (php_sapi_name() === 'cli-server' && is_file(__DIR__ . parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH))) {
    return false;
}

// Setup autoloading
require 'init_autoloader.php';

Setup ZF2 configuration files

In /config, create an application.config.php file using this file as the base. Update the modules entry as listed below.

return array(
    'modules' => array(
        'Application',
        // Vendor modules
        'DoctrineModule',
        'DoctrineORMModule',
    ),
);

Next, create local.php in the /config/autoload directory using the code below. The database credentials should be updated to match your environment.

<?php

return array(
    'doctrine' => array(
        'connection' => array(
            'orm_default' => array(
                'driverClass' => 'DoctrineDBALDriverPDOMySqlDriver',
                'params' => array(
                    'host'     => 'localhost',
                    'port'     => 3306,
                    'user'     => 'mysqlUser',
                    'password' => 'mysqlPassword',
                    'dbname'   => 'mysql_db',
                ),
            ),
        ),
        'configuration' => array(
            'orm_default' => array(
                // Generate proxies should be FALSE on production servers
                'generate_proxies' => true,
            ),
        ),
    ),
);

Setup ZF2 “Application” module

ZF2′s per-module configuration files make it extremely easy to more effectively organize your entities and repositories. In /module/Application/config, create a file named module.config.php using the code below.

<?php

return array(
    'doctrine' => array(
        'driver' => array(
            'application_driver' => array(
                'class' => 'DoctrineORMMappingDriverAnnotationDriver',
                'cache' => 'array',
                'paths' => array(
                    dirname(__DIR__) . '/src/Application/Entity',
                ),
            ),
            'orm_default' => array(
                'drivers' => array(
                    'ApplicationEntity' => 'application_driver',
                ),
            ),
        ),
    ),
);

Next, create Module.php in /module/Application. This file will become more important once the migration is complete.

<?php

namespace Application;

use ZendModuleManagerFeatureConfigProviderInterface;
use ZendModuleManagerFeatureAutoloaderProviderInterface;

class Module implements ConfigProviderInterface,
    AutoloaderProviderInterface
{
    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }

    public function getAutoloaderConfig()
    {
        return array(
            'ZendLoaderStandardAutoloader' => array(
                __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
            ),
        );
    }
}

Relocate existing entities and repositories to ZF2

Assuming your entities and repositories share a common namespace, create two new directories inside /module/Application/src/Application. The first directory should be entitled Entity, and the second, Repository. Next, we will need to move existing entities into the Application module and adjust any references to said entities accordingly.

For example, if you were using Model_Entity or ModelEntity as the prefix or namespace, it will need to be updated to ApplicationEntity. The same applies to repositories; if the prefix or namespace was Model_Repository or ModelRepository, it will become ApplicationRepository.

In summation, our data structures will follow this scheme:

Type Class Name Location
Entities ApplicationEntityEntityName /module/Application/src/Application/Entity
Repository ApplicationRepositoryEntityName /module/Application/src/Application/Repository

Make ZendMvcApplication available in ZF1

Add the following method to Bootstrap.php.

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initZf2()
    {
        $application = ZendMvcApplication::init(include 'config/application.config.php');

        Zend_Registry::set('zf2', $application);
        return $application;
    }
}

How to use ZF2′s DoctrineORMModule in ZF1

Merely creating the ZendMvcApplication object will ensure that Doctrine is properly setup based on the local.php configuration file. From here, using Doctrine is quite simple. By storing the ZendMvcApplication object in the much maligned Zend_Registry, it is universally accessible via Zend_Registry::get('zf2'). The ZF2 application object can also be accessed using $bootstrap->getResource('zf2').

The following code illustrates how to access Doctrine’s EntityManager, and retrieve an entity and repository.

/* @var $application ZendMvcApplication */
$application = Zend_Registry::get('zf2');
$sm = $application->getServiceManager();
$entityManager = $sm->get('DoctrineORMEntityManager');

// Get repository for EntityName
$repository = $entityManager->getRepository('ApplicationEntityEntityName');

// Find EntityName with ID #7
/* @var $entity ApplicationEntityEntityName */
$entity = $repository->find(7);

Conclusion

This “proxy-like” technique can be applied to many different ZF2 modules, making migration significantly less painful. If I left anything out, please let me know. Also, if you have a better way or other ideas on making migration easier, please feel free to post them.

2 thoughts on “Migrating to ZF2: Integrating Composer and DoctrineORMModule”

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>