i will try to explain you how you can create a command which you can use on your command line to execute doctrine commands. This is the first part of the post. The second part is a bit fantasy. I have tried to integrate doctrine to Zend_Tool and wanted to call it like:

zf.sh generate doctrine sql

Actually, you don’t need such a thing. First part is pretty much enough for you. Second part is just for fun.

So now let’s move on with the Doctrine CLI implementation first. I have read the documentation and their own implentation didn’t seem to work for me and I had to modify it a little bit. So you know the original file structure for a ZF project. If you still don’t then go here. We are creating another folder named as scripts. This is my personal choice, you may want to put it on the application folder too. In the scripts folder create a file name “doctrine”:

#!/usr/bin/php
<?php
define('BASEPATH','.'); // mockup that this app was executed from ci, taken from Doctrine documentation
chdir(dirname(__FILE__ . "/../")); //Be sure that your current directory should be the root (folder that contains scripts) directory, I dunno why but the other options failed for me...
require('doctrine.php');

Once you have created the file, and if you are on the linux environment (which if you are reading this you generally are) run this command for the file you just created can be run as a command line script:

chmod +x doctrine

So far nothing complicated. Now we must create the actual doctrine.php file which will do the trick. Again on the scripts folder create a file named “doctrine.php”:

<?php
require("public/header.php");

$application->bootstrap();

// Configure Doctrine Cli
// Normally these are arguments to the cli tasks but if they are set here the arguments will be auto-filled
$config = array(
    'data_fixtures_path'  =>  APPLICATION_PATH . '/data/fixtures',
    'models_path'         =>  APPLICATION_PATH . '/models/doctrine/',
    'migrations_path'     =>  APPLICATION_PATH . '/data/migrations',
    'sql_path'            =>  APPLICATION_PATH . '/data/sql',
    'yaml_schema_path'    =>  APPLICATION_PATH . '/data/schema',
    "generate_models_options" => array(
        "phpDocPackage"       => "Kartaca",
        "phpDocSubpackage"    => "Doctrine",
        "baseClassName"       => "Kartaca_Model_Doctrine_Record",
        "phpDocName"          => "Roy Simkes",
        "phpDocEmail"         => "roy@kartaca.com",
    ),
);

$cli = new Doctrine_Cli($config);
$cli->run($_SERVER['argv']);

As you should notice I have bootstrapped my whole application. Why? Honestly, why not? You should notice that, if you want to read from your database, update your database or to create files and folder you should define your file structure or your database connection to this file too. If you are an idealist and love to make unit tests, then you should do the same things on it’s bootstrapping file too. So you should create a real header file and include it on everywhere. You can find the includes of header.php on my github. But I’m sure you guess what it is.

So let’s go on with how this file works. We are bootstrapping our application, then we are creating a Doctrine_Cli object and give it some options. First five options are required if you want your files to be created on the right locations. I have created a data folder to hold things like sql, yaml or fixtures. You might prefer a similar structure or create your own but don’t forget to change them on the doctrine.php too.

The last option “generate_models_options” are needed if you want some custom modifications on the files which will be generated. phpDocPackage or phpDocName are not really needed however baseClassName might be very important for you to control your code. If you don’t tell it, every base model class which is created will be extended from Doctrine_Record class. Honestly it might not be an important if you are on a small scale project because you might never need to edit Doctrine_Record. However I strongly suggestyou to create a class named Kartaca_Model_Doctrine_Record, extend it from Doctrine_Record and leave it empty. It doesn’t really matter to write something in it. What matters, if you ever need to change the way Doctrine_Record works (forbid usage of some functions, or add additional functionality), you can do it easily and without having headaches.

So let’s give it a try and and run it like:

./doctrine

You should see the available commands you can use for the script now. If you haven’t go back to the previous steps and make sure you have done everything right. Now we will try to include this command to the Zend_Tool. Let me be honest with you, I made it however I don’t think I made it with the best way possible. I have looked to the documentation however it was pretty much outdated and I needed to look out for the actual code and I found my way as best as possible. Let me get to the code and you will see the problem later on.

On our library folder, we create this Kartaca/Tool/ folder and on it we create a file name DoctrineProvider.php:

<?php
require_once("Zend/Tool/Framework/Provider/Interface.php");
require_once("Doctrine.php");

class Kartaca_Tool_DoctrineProvider
    implements Zend_Tool_Framework_Provider_Interface,
               Zend_Tool_Framework_Registry_EnabledInterface
{

    private $_registry = null;

    /**
     *
     * @var Doctrine_Cli
     */
    private $_cli = null;

    protected function _setUp()
    {
        if (file_exists("public/header.php")) {
            require 'public/header.php';
            $application->bootstrap();
        } else {
            require_once 'Zend/Tool/Framework/Provider/Exception.php';
            throw new Zend_Tool_Framework_Provider_Exception("You need to specify a project specific Zend_Application which contains database connection values for doctrine");
        }

        $config = array(
            'data_fixtures_path'  =>  'application/data/fixtures',
            'models_path'         =>  'application/models/doctrine/',
            'migrations_path'     =>  'application/data/migrations',
            'sql_path'            =>  'application/data/sql',
            'yaml_schema_path'    =>  'application/data/schema',
            "generate_models_options" => array(
                "phpDocPackage"       => "Kartaca",
                "phpDocSubpackage"    => "Doctrine",
                "baseClassName"       => "Kartaca_Model_Doctrine_Record",
                "phpDocName"          => "Roy Simkes",
                "phpDocEmail"         => "roy@kartaca.com",
            ),
        );
        $this->_cli = new Doctrine_Cli($config);
    }

    public function setRegistry(Zend_Tool_Framework_Registry_Interface $reg)
    {
        $this->_registry = $reg;
    }

    public function create($task)
    {
        $this->_setUp();
        $this->_cli->run(array(0 => "doctrine", 1 => $task));
    }
}

Notice that create function is equivalent to create part in “zf create doctrine generate-sql”. $task argument it takes is coming as generate-sql (the third argument of zf command). You might add additional arguments to the function and your command will ask for additional arguments.

I have not got into the details of implementing wrappers for the doctrine commands. It was only a burden to me and the point was not that. You can do something like:

public function generate($type) {
     switch($type) {
         case "sql":
             $this->_cli->run(array(0 => '', 1 => "generate-sql"));
             break;
         default:
             throw new Zend_Tool_Framework_Provider_Exception("Invalid Type");
     }
}

by this way, you can create sqls like: “zf generate doctrine sql”.

I had told that we need a secod file Manifest.php. It’s like:

<?php

require_once("Zend/Tool/Framework/Manifest/Interface.php");
require_once("Zend/Tool/Framework/Manifest/ProviderManifestable.php");
require_once("Zend/Tool/Framework/Manifest/MetadataManifestable.php");
require_once(dirname(__FILE__) . "/DoctrineProvider.php");

/**
 * Description of Manifest
 *
 * @author roysimkes
 */
class Kartaca_Tool_Manifest
    implements Zend_Tool_Framework_Manifest_Interface,
               Zend_Tool_Framework_Manifest_ProviderManifestable,
               Zend_Tool_Framework_Manifest_MetadataManifestable
{
    public function getProviders()
    {
        return array(
            new Kartaca_Tool_DoctrineProvider(),
        );
    }

    public function getMetadata()
    {
        return array(
            new Zend_Tool_Framework_Metadata_Basic(
                array(
                    "name" => "argv",
                    "value" => $_SERVER['argv']
                )
            ),
        );
    }
}

What did I do here? For you to be able to extend Zend_Tool you need to create a Provider (you can have more than one of course) and then a Manifest class. Manifest class is used to define what kind of providers is used on the project. It can also contain Metadata information (arguments coming from command line interface, or everything which can be used as data in a provider) you can do this by implementing Zend_Tool_Framework_Manifest_MetadataManifestable. A manifest to really work like a manifest should implement the other two classes. With this manifest file zf command line tool now knows what kind of providers there is. If you put these on a global php include path you will be able to use them for all the projects otherwise only in this one (or the ones which these two classes are included).

Provider is the where the magic happens. It redirects the command given to the Doctrine_Cli and it works just like you have called it as ./doctrine.

This was the end of implementation but it was not the best one you could have found. There is also a problem that this two classes are application specific and it’s hard to move them to something which is not. and you should recreate them on every project you have made. So we change my previous examples like this:


require_once("Zend/Tool/Framework/Provider/Interface.php");
require_once("Doctrine.php");

class Kartaca_Tool_DoctrineProvider
    implements Zend_Tool_Framework_Provider_Interface,
               Zend_Tool_Framework_Registry_EnabledInterface
{

    private $_registry = null;

    /**
     *
     * @var Doctrine_Cli
     */
    private $_cli = null;

    protected function _setUp()
    {
        $applicationBootstrapFile = $this->_registry->getManifestRepository()->getMetadata(array("name" => "application_bootstrap_file"))->getValue();
        if (file_exists($applictionBootstrapFile)) {
            require $applicationBootstrapFile;
            $application->bootstrap(); //badly hardcoded...
        } else {
            require_once 'Zend/Tool/Framework/Provider/Exception.php';
            throw new Zend_Tool_Framework_Provider_Exception("You need to specify a project specific Zend_Application which contains database connection values for doctrine");
        }
        $config = $this->_registry->getManifestRepository()->getMetadata(array("name" => "cli_options"))->getValue();
        $this->_cli = new Doctrine_Cli($config);
    }

    public function setRegistry(Zend_Tool_Framework_Registry_Interface $reg)
    {
        $this->_registry = $reg;
    }

    public function create($task)
    {
        $this->_setUp();
        $this->_cli->run(array(0 => "doctrine", 1 => $task));
    }
}

and this:

require_once("Zend/Tool/Framework/Manifest/Interface.php");
require_once("Zend/Tool/Framework/Manifest/ProviderManifestable.php");
require_once("Zend/Tool/Framework/Manifest/MetadataManifestable.php");
require_once(dirname(__FILE__) . "/DoctrineProvider.php"); //we still need a fix for this...

/**
 * Description of Manifest
 *
 * @author roysimkes
 */
class Kartaca_Tool_Manifest
    implements Zend_Tool_Framework_Manifest_Interface,
               Zend_Tool_Framework_Manifest_ProviderManifestable,
               Zend_Tool_Framework_Manifest_MetadataManifestable
{
    public function getProviders()
    {
        return array(
            new Kartaca_Tool_DoctrineProvider(),
        );
    }

    public function getMetadata()
    {
        return array(
            new Zend_Tool_Framework_Metadata_Basic(
                array(
                    "name" => "cli_options",
                    "value" => array(
                        'data_fixtures_path'  =>  'application/data/fixtures',
                        'models_path'         =>  'application/models/doctrine/',
                        'migrations_path'     =>  'application/data/migrations',
                        'sql_path'            =>  'application/data/sql',
                        'yaml_schema_path'    =>  'application/data/schema',
                        "generate_models_options" => array(
                            "phpDocPackage"       => "Kartaca",
                            "phpDocSubpackage"    => "Doctrine",
                            "baseClassName"       => "Kartaca_Model_Doctrine_Record",
                            "phpDocName"          => "Roy Simkes",
                            "phpDocEmail"         => "roy@kartaca.com",
                        ),
                    )
                )
            ),
            new Zend_Tool_Framework_Metadata_Basic(
                array(
                    "name" => "application_bootstrap_file",
                    "value" => "public/header.php",
                )
            ),
        );
    }
}

So when I have done this, I have moved everything project specific to the manifest file instead of the provider and my provider has become a generic script which can be used everywhere. Only thing I should do is to move provider file to the php include path and move the manifest file to the project specific include paths.

Things got ugly and I was unable to find a proper way on some points. One thing was the options found in zfprojext.xml (a hidden file created when you run command zf create project foo). There was an option named “projectProvidersDirectory” set as disabled. I couldn’t find a way to enable it (actually a way to add folders to it). I suppose by this way, you can define project specific providers and manifests and this solves the problems I have mostly encountered.
There is no autoloading mechanism in Zend_Tool so don’t forget to activate such a thing if you need (if you intend to use doctrine standalone without the bootstrap file, then you should use spl_autoload_register(array(“Doctrine”, “autoload”) on your _setUp() instead of require()).

There was only one source I was able to find which explained something about Zend_Tool here and I mostly browsed the code. Documentation seems pretty outdated (even the slide is outdated).

There is also a proposal about this integration here. If you are desperate, and my code is not ok with you (which I don’t think you are) you can push them to include it on a newer release of ZF.

i don’t think the second part of this writing was not much useful but you may need to extend Zend_Tool and this might be useful. Or like me you may want to do it, just for the sake of doing it :)

I have added all the codes i have used in this post on my github too. You can find it here

Kingdom of Roi: http://roysimkes.net/blog/2009/12/creating-doctrine-cli-and-adding-it-to-zend_tool/