How to Create Custom Doctrine Models for Pluggable ZF Modules

Well, in my previous post, I had mentioned about “pluggable” modules for Zend Framework. They had their own, configuration files, controllers and views. However most generally, you also need database tables and models. So there should be a way to define them too. It’s easy if you are making your ORM manually. However for the ones who use Doctrine out there, things are not that simple.

Doctrine’s documentation is not very well and you might get confused when you look for something. And in nowhere I found if there was a way to use different yaml files to create models. We have our own default models and schemes but also our modules’ models and schemes too. So they must be generated and maintained individually however they also have connections to the other tables (models) defined in the default scheme file and relations must be made. So what should we do?

But first let’s remember (the fifth of november?) what we had previously and what we want to add.

We had defined a schema for users and emails. One user may had one or many emails. But let’s say that you want to limit this email boxes’ size (they were not actually email boxes but let’s assume so :). So what you need to do is to create a module, a database table, and a doctrine model related with the original Email. We had already created our module and configuration files in the previous posts. We are only missing the Doctrine part. We create the required folders like data and data/schema in our module’s directory (check my github for the actual directory structure or read below).

So let’s create our yaml file for the module and save it in the data/schema folder as schema.yml:

options:
  type: INNODB
  collate: utf8_unicode_ci
  charset: utf8
Email_Quota:
  actAs: [SoftDelete]
  connection: doctrine
  tableName: email_quota
  columns:
    id:
      type: integer(8)
      fixed: false
      unsigned: false
      primary: true
      autoincrement: true
    email_id:
      type: integer(8)
      notnull: true
    quota:
      type: integer(8)
      notnull: true
    deleted_at:
      type: timestamp
      notnull: false
  relations:
    Email:
      local: email_id
      foreign: id
      class: Email
      alias: Email
  indexes:
    email_index:
      fields: [email_id]

Code speaks for itself. What we should mention is this: As there is no definition about Email in the actual yaml file, if you don’t define the index and relation’s details in the yaml, the relation won’t be created. Don’t forget to create the index for the email_id field or the relation won’t work as intended again.

Cool, now we have a beautiful schema file but we should be able to use it individually without getting involved with the default module. So we should be able to call the doctrine-cli for our module only. Remember that we had our own doctrine command line script. We have to do some modifications to it. All it was doing was to run the application bootstrap, define the locations of the folders required for the script and run the command we had given. What we need to do is to be able to change the default locations of the folders according to some parameters we will be giving. So change your project-root/scripts/doctrine.php file like this:

bootstrap();

$defaultPath = APPLICATION_PATH;

$args = $_SERVER["argv"];

//Perhaps, I want to create models and tables or create sqls for just a module!!!
//Usage: ./doctrine -m MODULE_NAME generate-models-yaml
//The above usage will only generate models for the yaml defined in the module directory
$moduleNameIndex = array_search("-m", $args);
if ($moduleNameIndex !== false) { //Check if the module bootstrapping exists or not...
    $defaultPath = APPLICATION_PATH . "/modules/" . $args[$moduleNameIndex+1];
    //Now unset these module variables to prevent causing problems in the doctrine command line
    array_splice($args, $moduleNameIndex, 2);
}

// 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'  =>  $defaultPath . '/data/fixtures',
    'models_path'         =>  $defaultPath . '/models/doctrine/',
    'migrations_path'     =>  $defaultPath . '/data/migrations',
    'sql_path'            =>  $defaultPath . '/data/sql',
    'yaml_schema_path'    =>  $defaultPath . '/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($args);

Yet another code block who loves to speak for itself. Now you can run all the commands you can run for your actual application, for a specific module too. Once you run ./doctrine -m custom generate-models-yaml command you will notice that required Doctrine models are created in the paths we have defined in the doctrine.php file.

You can also create migrations, sqls and fixtures for your module. However you cannot modify an already created model yet. You cannot override an already created model too as the scope of this command is limited to the module. However you should be able to extend an already created model and make some configuration for your model to be used application wide instead of the model you have just extended. This might cause problems and module conflicts however might be a cool feature if you know what you are doing. But that can be the topic for another blog post. :)

As always I have already uploaded the code example to my github and you can check it out there.