Archive for the ‘Software Development’ Category

jsDoctrineSchemaOverriderPlugin

Tuesday, September 7th, 2010

Why I Wrote This Plugin

In order to understand what this plugin is for, it’s helpful to have a little background. There are two ways to handle your schema in symfony:

Method 1: Write your config/doctrine/schema.yml by hand (for this post we’ll assume you’re using Doctrine) and run symfony doctrine:build-sql to generate your database tables. This is what most people do.

Method 2: Create your database tables by hand and run symfony doctrine:build-schema to generate your schema. This is what I do.

Why do I generate my schema from my database instead of generating my database from my schema? There’s one big reason. Every time you run symfony doctrine:insert-sql, your entire database—meaning now just the database structure, but the data as well—gets wiped out. The database structure is put right back in place but if you want to keep your data intact, you’re on your own. Doing a mysqldump only works for the most trivial changes. Because the symfony developers are well aware that you’re going to want your data to stay intact, they let you use what are called fixtures—sets of data that you can easily load into your database—but it seems terribly inefficient to have to manually shuffle around your fixture data each time you make a change to your database structure. Plus, what if you like to periodically load your production data into your development database so you’re working with a realistic dataset? As far as I know, fixtures don’t accommodate that. Those are the main reasons why I use build-schema instead of build-sql. (From now on, I will call these two methods the build-from-database method and the build-from-schema method.)

Neither the build-from-database method or the build-from-schema method is perfect. (I just find build-from-database “less bad.”) I’ve come across the following problems with build-from-database:

  • If any of my entities inherit from other entities (read about that here), the relationship gets wiped out every time I build the schema
  • If I have SQL views in my database, each view shows up as a model, which doesn’t always make sense
  • If I have, say, a WordPress install as part of my website, all my WordPress tables show up as models (I know I could work around this by putting the WP install in a separate database. The point is that every table in your database will get translated into a model, regardless of whether you want them to or not

These are the main problems I’ve had with the build-from-database method. The most frustrating part is that I would have to manually correct my schema every single time I ran build-schema! It gets old fast, as I’m sure you know if you do build-from-database instead of build-from-schema.

How jsDoctrineSchemaOverriderPlugin Fixes The Problem

The brilliant part of symfony’s model design is the idea where there’s a base class and an inheriting class for each model. For example, if you have a table in your database called animal, symfony creates an Animal class as well as a BaseAnimal class. Then, when you re-run symfony doctrine:build-model, BaseAnimal is wiped out and rewritten but Animal—the class you’re allowed to custmize—is left undisturbed. If you’re familiar with this idea, you’ll understand how jsDoctrineSchemaOverriderPlugin works.

The meat of jsDoctrineSchemaOverriderPlugin is in two classes: BaseSchema and Schema. Like symfony’s models, BaseSchema is auto-generated and Schema is there for you to override BaseSchema. BaseSchema is automatically built by jsDoctrineSchemaOverriderPlugin as a direct reflection of your database structure and Schema is where you can edit that structure. And get this: you don’t have to re-do all your work each time you rebuild the schema!

Installation

$ symfony plugin:install http://jasonswett.net/jsDoctrineSchemaOverriderPlugin-0.1.0.tgz

Examples

Removing an Entity

Let’s first take a trivial example: you have an view called my_view that you don’t want a model for. Edit your plugins/jsDoctrineSchemaOverriderPlugin/lib/Schema.class.php by adding a line to unset this view:

<?php
class Schema extends BaseSchema
{
  public function configure()
  {
    unset($this->entities['MyView'];
  }
}

Now, instead of running symfony doctrine:build-schema, you’ll do something a little different. Run:

$ symfony schema-overrider:build-schema

You’ll want to always run schema-overrider:build-schema instead of doctrine:build-schema. This ensures that the overrides you make in Schema.class.php are reflected in your schema.yml.

That’s the simplest case: completely wiping out an entity. What if we want to edit an entity’s attributes?

Overriding Attributes

Let’s say we have two classes, Animal and Horse, with Horse inheriting from Animal. If you just run symfony doctrine:build-schema, symfony will have no idea just by looking at your database schema that Horse inherits from Animal. You can make sure symfony knows about this inheritance by adding the following to plugins/jsDoctrineSchemaOverriderPlugin/lib/Schema.class.php:

<?php
class Schema extends BaseSchema
{
  public function configure()
  {
    $this->entities['Horse'] = array(
      'connection' => 'doctrine',
      'tableName' => 'horse',
      'inheritance' => array(
        'extends' => 'Animal',
        'type' => 'concrete',
      ),  
    );
  }
}

Just like in the first example, we’ll run this command:

$ symfony schema-overrider:build-schema

Now Horse will always inherit from Animal and you won’t have to keep copying and pasting every time you run build-schema.

Please Help!

If you have any feedback of any kind about this plugin, please feel free to leave me a comment and let me know. I don’t know if I’m going about this the right way and I don’t know if my documentation is clear enough for other people to be able to use my plugin. All I know is that it works for me and it saves me a ton of work. Any feedback you have would be greatly appreciated!

What to do if you get ERROR 1005/errno: 150

Friday, September 3rd, 2010

Sometimes when you try to add a foreign key in MySQL, you get this error:

ERROR 1005 (HY000): Can't create table 'my_table.#sql-a74_233' (errno: 150)

Every time I’ve come across this problem it’s been because my int types didn’t match. For example, I had an int(11) trying to reference a bigint(20) on a different table. If you change the int types to match, the problem goes away. Keep in mind that you may have to delete all the rows in your tables and/or remove existing foreign keys in order to be able to make the change.

Transactions with Doctrine

Friday, September 3rd, 2010

If you’re going to make multiple related updates to your database, you should use a transaction. Here’s how to do a transaction with Doctrine. This is essentially lifted from the official Doctrine documentation but it’s a little more to-the-point than theirs. Plus my version doesn’t fail silently.

<?php
$conn = Doctrine_Manager::connection();
 
try
{
  $conn->beginTransaction();
 
  $myObject->save();
  $myOtherObject->save();
 
  $conn->commit();
}
catch(Exception $e)
{
  throw new Exception($e->getMessage());
  $conn->rollback();
}

How to validate and sanitize a URL in symfony

Friday, September 3rd, 2010

This validator works for symfony 1.4 and is not necessarily backward compatible.

If you’d like to both validate and sanitize a URL in symfony, it’s pretty easy. First, put the following code in lib/myValidatorUrl.class.php:

<?php
 
/**
 * myValidatorUrl validates and sanitizes a URL.
 *
 * @author Jason Swett (http://jasonswett.net/how-to-validate-and-sanitize-a-url-in-symfony)
 */
class myValidatorUrl extends sfValidatorUrl
{
  protected function doClean($value)
  {
    $clean = (string) $value;
 
    // If the URL doesn't start with "http", add "http://".
    if (substr($clean, 0, 4) != 'http')
    {
      $clean = 'http://'.$clean;
    }
 
    // Add a trailing slash if the URL doesn't have one.
    if (substr($clean, -1, 1) != '/')
    {
      $clean .= '/';
    }
 
    // If the URL still isn't valid after that, it probably wasn't close enough to begin with.
    if (!preg_match($this->generateRegex(), $clean))
    {
      throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }
 
    return $clean;
  }
}

Then, in the form where you have your URL field, add the following line to your configure() method:

$this->validatorSchema['url'] = new myValidatorPhone(array('required' => false));

That’s all! Now, if someone enters a URL like “example.com”, it will get saved as “http://example.com/”.

How to validate and sanitize a phone number in symfony

Monday, May 31st, 2010

This validator works for symfony 1.4 and is not necessarily backward compatible.

If you’d like to both validate and sanitize a phone number in symfony, it’s pretty easy. First, put the following code in lib/sfValidatorPhone.class.php:

<?php
 
/**
 * sfValidatorPhone validates a phone number.
 *
 * @author Jason Swett (http://jasonswett.net/how-to-validate-a-phone-number-in-symfony)
 */
class sfValidatorPhone extends sfValidatorBase
{
  protected function doClean($value)
  {
    $clean = (string) $value;
 
    $phone_number_pattern = '/^(^(1\s*[-\/\.]?)?(\((\d{3})\)|(\d{3}))\s*[-\/\.]?\s*(\d{3})\s*[-\/\.]?\s*(\d{4})\s*(([xX]|[eE][xX][tT])\.?\s*(\d+))*$)*$/';
 
    // If the value isn't a phone number, throw an error.
    if (!preg_match($phone_number_pattern, $clean))
    {
      throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }
 
    // Take out anything that's not a number.
    $clean = preg_replace('/[^0-9]/', '', $clean);
 
    // Split the phone number into its three parts.
    $first_part = substr($clean, 0, 3);
    $second_part = substr($clean, 3, 3);
    $third_part = substr($clean, 6, 4);
 
    // Format the phone number.
    $clean = '('.$first_part.') '.$second_part.'-'.$third_part;
 
    return $clean;
  }
}

Then, in the form where you have your phone number field, add the following line to your configure() method:

$this->validatorSchema['phone'] = new sfValidatorPhone(array('required' => false));

That’s all! Now, if someone enters a number like 123.456.7890, it will get saved as (123) 456-7890.

Changing your title in symfony

Friday, May 28th, 2010

The following is for symfony 1.4 and is not necessarily backward compatible.

If you just want to completely change your title in symfony and you’re okay with wiping out whatever’s already there, it’s simple. Just use this in your action:

$this->getResponse()->setTitle('My New Title');

But what if you want to keep the first part of your app’s title and only change the rest? That’s also pretty easy. In apps/frontend/templates/layout.php, just change this

<?php include_title() ?>

to this:

<title>The Static Part of Your Title <?php echo $sf_response->getTitle() ?></title>

Now when you do this in your action

$this->getResponse()->setTitle('My New Title');

The “The Static Part of Your Title” will still be there and only the rest of it will have changed.

How to Make the Command Line Behave Like vim

Tuesday, May 4th, 2010

If you use vim, you’ve probably found yourself wishing you could use vim commands in more places. Well, the command line is one place where you can. Simply run this simple command and you’ll have vim commands at your command on the command line:

set -o vi

Better yet, add that line to your ~/.bashrc on Linux or your ~/.bash_profile on Mac OS and you will have permanently added this wonderful ability to your terminal.

How to Turn Colors On in Git

Tuesday, May 4th, 2010

To turn colors on in Git, just run the following command:

$ git config --global color.ui "auto"

Creating a Remote Repository with Git

Saturday, March 6th, 2010

Creating a remote repository with Git is really easy but all the tutorials I’ve seen out there make it harder than it has to be. Here are some simpler instructions.

For the purposes of this example, we’re assuming:

  • You have Git installed on both your local and remote machines
  • Your username is “bob”
  • Your remote host is example.com
  • You have shell access to your remote host

First you need to create a repository on your remote machine. We’re going to do this in /home/bob/my-project.

$ cd /home/bob
$ mkdir my-project
$ cd my-project/
$ git init
Initialized empty Git repository in /home/jason/my-project/.git/

Your Git repository now exists. Right now it’s empty—let’s add one file so we have something to check for when we clone the repository to our local machine.

$ touch file1
$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add ..." to include in what will be committed)
#
#	file1
nothing added to commit but untracked files present (use "git add" to track)
$ git add file1
$ git commit -m "Initial commit."
[master (root-commit) 04df0da] Initial commit.
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file1

Now we have one file, file1, in our Git repository.

Although we now have a perfectly good repository, that’s not actually the one we’re going to clone from our local machine. We’re going to create what’s called a bare repository for that. According to Jon Loeliger’s excellent Version Control with Git: Powerful Tools and Techniques for Collaborative Software Development, “If you set up a repository into which developers push changes, it should be bare.” That’s what we want, so that’s what we’ll do.

$ mkdir /var/git
$ cd /var/git
$ git clone --bare /home/jason/my-project/ my-project.git
Initialized empty Git repository in /var/git/my-project.git/

Note: There’s nothing special about the directory /var/git. We could have chosen anything and it would have worked just as well.

Now we have a bare repository in /var/git/myproject.git. This is the repository from which we’ll clone our local repository using the git clone command:

$ git clone ssh://bob@example.com/var/git/my-project.git my-project
Initialized empty Git repository in /var/www/my-project/.git/
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.

If everything worked correctly, you should have gotten the same results I did. Let’s take a look at the repository to see with our own eyes that it came across okay:

$ cd my-project/
$ git status
# On branch master
nothing to commit (working directory clean)
$ ls
file1

There’s our old friend file1. Now that we’ve cloned the repository to our local machine, can we push changes back to the remote repository?

$ touch file2
$ git add file2
$ git commit -m "Added file2."
Created commit fa20318: Added file2.
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file2
$ git push origin master
Counting objects: 3, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 239 bytes, done.
Total 2 (delta 0), reused 0 (delta 0)
To ssh://bob@example.com/var/git/my-project.git
   04df0da..fa20318  master -> master

That seemed to go well. Let’s go back to that first repository we created—not the bare one but the very first one&dmash;and see if these changes come through in a git pull.

$ cd /home/bob/my-project/
$ git pull /var/git/my-project.git/
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (2/2), done.
From /var/git/my-project
 * branch            HEAD       -> FETCH_HEAD
Updating 04df0da..fa20318
Fast forward
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file2
$ ls
file1  file2

There you have it. You’re done!

And by the way, check out this book. It really is great:

Inheritance with symfony and Doctrine ORM

Monday, February 1st, 2010

The Problem

Doctrine ORM claims to support some kind of inheritance but I have yet to see a good example. The inheritance documentation on the Doctrine ORM site could be worse but it certainly has a lot of room for improvement. How about an example with some actual data? The symfony documentation for Doctrine inheritance lays things out a little more clearly than the Doctrine docs but it still wants for good examples.

The Solution

Here I intend to perform a hearty exploration of Doctrine inheritance with the hope that I can show, by example, what it can and can’t do. The inheritance model I will use will be simple: the base class will be called FarmAnimal and the three inheriting classes will be Cow, Dog and Chicken. All FarmAnimals will have at least a name, a sound and a number of legs. Each type of FarmAnimal will have the following unique properties: Cow will have a use (beef or dairy), a Dog will have a breed and a Chicken will have an egg color.

If you’d like to follow along, I’ve put together some setup instructions for this project.

Doctrine offers three styles of inheritance: Simple, Concrete and Column Aggregation. The symfony docs give the following definitions:

Name Description
Concrete Each child class has a separate table has all the columns of its parents
Simple Each child class shares the same table and columns as its parents
Column Aggregation All columns must be defined in the parent and each child class is determined by a type column

Simple Inheritance

The Doctrine documentation shows an example where we have a User class and a Group class. Both of these classes inherit from Entity and neither one has any unique properties. When the example schema is evaluated, Doctrine only spits out one table: entity. If that’s all that “simple inheritance” is good for, I don’t really see how that’s inheritance. Any object you might instantiate, whether it be a User, Group or plain Entity, is an Entity, nothing more and nothing less. What’s gained there?

Let’s try something similar to their example but go a little further with it.

config/doctrine/schema.yml

FarmAnimal:
  columns:
    name: string(20)
    sound: string(20)
    leg_count: integer
    created_at: timestamp
    updated_at: timestamp
 
Cow:
  inheritance:
    extends: FarmAnimal
    type: simple
  columns:
    purpose:
      type: enum
      values: [beef, dairy]
 
Dog:
  inheritance:
    extends: FarmAnimal
    type: simple
  columns:
    breed: string(20)
 
Chicken:
  inheritance:
    extends: FarmAnimal
    type: simple
  columns:
    egg_color:
      type: enum
      values: [brown, white]

Since we’re using enums, we’ll have to update config/databases.yml and add the use_native_enum directive:

all:
  doctrine:
    class: sfDoctrineDatabase
    param:
      dsn: 'mysql:host=localhost;dbname=inheritance'
      username: inheritance
      password: pass123
      attributes:
        use_native_enum: true

Now let’s build the model, build the SQL and insert the SQL:

$ symfony doctrine:build-model
$ symfony doctrine:build-sql
$ symfony doctrine:insert-sql

What does this give us in the database?

mysql> SHOW TABLES;
+-----------------------+
| Tables_in_inheritance |
+-----------------------+
| farm_animal           |
+-----------------------+
1 row IN SET (0.00 sec)
 
mysql> DESCRIBE farm_animal;
+------------+-----------------------+------+-----+---------+----------------+
| FIELD      | Type                  | NULL | KEY | DEFAULT | Extra          |
+------------+-----------------------+------+-----+---------+----------------+
| id         | bigint(20)            | NO   | PRI | NULL    | AUTO_INCREMENT |
| name       | varchar(20)           | YES  |     | NULL    |                |
| sound      | varchar(20)           | YES  |     | NULL    |                |
| leg_count  | bigint(20)            | YES  |     | NULL    |                |
| created_at | datetime              | YES  |     | NULL    |                |
| updated_at | datetime              | YES  |     | NULL    |                |
| purpose    | enum('beef','dairy')  | YES  |     | NULL    |                |
| breed      | varchar(20)           | YES  |     | NULL    |                |
| egg_color  | enum('brown','white') | YES  |     | NULL    |                |
+------------+-----------------------+------+-----+---------+----------------+
9 rows IN SET (0.01 sec)

As you can clearly see, all the inheriting classes’ properties have been rolled into one table along with the base class’s properties. This is exactly what the Doctrine doc tells us, but it doesn’t totally spell it out with an example.

The table above is a bad model, both from a purist and pragmatic perspective. A purist might say, “We have three distinctly different types of things but only one data structure! Are we expected to deduce, based on which values are NULL and which are not, which type of FarmAnimal we’re dealing with? What if we add the idea of a Cat with a breed? How do we tell it apart from a Dog?” And a pragmatist might say, “What if we have 20 different types of FarmAnimals, each having 5 different properties? That’s at least 100 columns in the FarmAnimal table!” I would agree with both the pragmatist and the purist. Imagine looking at a table with 100 columns, most of the rows having NULL in most of the columns most of the time. That’s an extreme example, but the principle is the same on any scale.

If you look at the PHP classes created by symfony, they’re meaningless. Take a look at lib/model/doctrine/base/BaseChicken.class.php, for example:

/**
 * BaseChicken
 *
 * This class has been auto-generated by the Doctrine ORM Framework
 *
 *
 * @package    INHERITANCE
 * @subpackage model
 * @author     Your name here
 * @version    SVN: $Id: Builder.php 6820 2009-11-30 17:27:49Z jwage $
 */
abstract class BaseChicken extends FarmAnimal
{
    public function setUp()
    {
        parent::setUp();
 
    }
}

There’s nothing unique to Chicken there. All the properties of all the FarmAnimals are kept in lib/model/doctrine/base/BaseFarmAnimal.class.php:

abstract class BaseFarmAnimal extends sfDoctrineRecord
{
    public function setTableDefinition()
    {
        $this->setTableName('farm_animal');
        $this->hasColumn('name', 'string', 20, array(
             'type' => 'string',
             'length' => '20',
             ));
        $this->hasColumn('sound', 'string', 20, array(
             'type' => 'string',
             'length' => '20',
             ));
        $this->hasColumn('leg_count', 'integer', null, array(
             'type' => 'integer',
             ));
        $this->hasColumn('created_at', 'timestamp', null, array(
             'type' => 'timestamp',
             ));
        $this->hasColumn('updated_at', 'timestamp', null, array(
             'type' => 'timestamp',
             ));
        $this->hasColumn('purpose', 'enum', null, array(
             'type' => 'enum',
             'values' =>
             array(
              0 => 'beef',
              1 => 'dairy',
             ),
             ));
        $this->hasColumn('breed', 'string', 20, array(
             'type' => 'string',
             'length' => '20',
             ));
        $this->hasColumn('egg_color', 'enum', null, array(
             'type' => 'enum',
             'values' =>
             array(
              0 => 'brown',
              1 => 'white',
             ),
             ));
    }
 
    public function setUp()
    {
        parent::setUp();
 
    }
}

I’ve seen enough of “simple inheritance” to know that I would never use it. Let’s see what “concrete inheritance” is all about.

Concrete Inheritance

Apparently all that’s needed to switch from simple inheritance to concrete inheritance is to replace all instances of type: simple with type: concrete:

FarmAnimal:
  columns:
    name: string(20)
    sound: string(20)
    leg_count: integer
    created_at: timestamp
    updated_at: timestamp
 
Cow:
  inheritance:
    extends: FarmAnimal
    type: concrete
  columns:
    purpose:
      type: enum
      values: [beef, dairy]
 
Dog:
  inheritance:
    extends: FarmAnimal
    type: concrete
  columns:
    breed: string(20)
 
Chicken:
  inheritance:
    extends: FarmAnimal
    type: concrete
  columns:
    egg_color:
      type: enum
      values: [brown, white]

Let’s run our symfony commands again:

$ symfony doctrine:build-model
$ symfony doctrine:build-sql
$ symfony doctrine:insert-sql

What’s in the database now?

mysql> SHOW TABLES;
+-----------------------+
| Tables_in_inheritance |
+-----------------------+
| chicken               |
| cow                   |
| dog                   |
| farm_animal           |
+-----------------------+
4 rows IN SET (0.00 sec)

That’s encouraging: each class has its own separate table.

mysql> DESCRIBE farm_animal;
+------------+-------------+------+-----+---------+----------------+
| FIELD      | Type        | NULL | KEY | DEFAULT | Extra          |
+------------+-------------+------+-----+---------+----------------+
| id         | bigint(20)  | NO   | PRI | NULL    | AUTO_INCREMENT |
| name       | varchar(20) | YES  |     | NULL    |                |
| sound      | varchar(20) | YES  |     | NULL    |                |
| leg_count  | bigint(20)  | YES  |     | NULL    |                |
| created_at | datetime    | YES  |     | NULL    |                |
| updated_at | datetime    | YES  |     | NULL    |                |
+------------+-------------+------+-----+---------+----------------+
6 rows IN SET (0.00 sec)
 
mysql> DESCRIBE cow;
+------------+----------------------+------+-----+---------+----------------+
| FIELD      | Type                 | NULL | KEY | DEFAULT | Extra          |
+------------+----------------------+------+-----+---------+----------------+
| id         | bigint(20)           | NO   | PRI | NULL    | AUTO_INCREMENT |
| name       | varchar(20)          | YES  |     | NULL    |                |
| sound      | varchar(20)          | YES  |     | NULL    |                |
| leg_count  | bigint(20)           | YES  |     | NULL    |                |
| created_at | datetime             | YES  |     | NULL    |                |
| updated_at | datetime             | YES  |     | NULL    |                |
| purpose    | enum('beef','dairy') | YES  |     | NULL    |                |
+------------+----------------------+------+-----+---------+----------------+
7 rows IN SET (0.00 sec)
 
mysql> DESCRIBE dog;
+------------+-------------+------+-----+---------+----------------+
| FIELD      | Type        | NULL | KEY | DEFAULT | Extra          |
+------------+-------------+------+-----+---------+----------------+
| id         | bigint(20)  | NO   | PRI | NULL    | AUTO_INCREMENT |
| name       | varchar(20) | YES  |     | NULL    |                |
| sound      | varchar(20) | YES  |     | NULL    |                |
| leg_count  | bigint(20)  | YES  |     | NULL    |                |
| created_at | datetime    | YES  |     | NULL    |                |
| updated_at | datetime    | YES  |     | NULL    |                |
| breed      | varchar(20) | YES  |     | NULL    |                |
+------------+-------------+------+-----+---------+----------------+
7 rows IN SET (0.01 sec)
 
mysql> DESCRIBE chicken;
+------------+-----------------------+------+-----+---------+----------------+
| FIELD      | Type                  | NULL | KEY | DEFAULT | Extra          |
+------------+-----------------------+------+-----+---------+----------------+
| id         | bigint(20)            | NO   | PRI | NULL    | AUTO_INCREMENT |
| name       | varchar(20)           | YES  |     | NULL    |                |
| sound      | varchar(20)           | YES  |     | NULL    |                |
| leg_count  | bigint(20)            | YES  |     | NULL    |                |
| created_at | datetime              | YES  |     | NULL    |                |
| updated_at | datetime              | YES  |     | NULL    |                |
| egg_color  | enum('brown','white') | YES  |     | NULL    |                |
+------------+-----------------------+------+-----+---------+----------------+
7 rows IN SET (0.00 sec)

That looks like what I want. Each entity is represented by a table that has a column for each of its properties, nothing more and nothing less. The only way this might be improved, in my opinion, is if we could tell Doctrine that FarmAnimal is an abstract class and we don’t want a table for it.

If we look at the symfony-generated classes, they look how we’d expect:

abstract class BaseChicken extends FarmAnimal
{
    public function setTableDefinition()
    {
        parent::setTableDefinition();
        $this->setTableName('chicken');
        $this->hasColumn('egg_color', 'enum', null, array(
             'type' => 'enum',
             'values' =>
             array(
              0 => 'brown',
              1 => 'white',
             ),
             ));
    }
 
    public function setUp()
    {
        parent::setUp();
 
    }
}
abstract class BaseFarmAnimal extends sfDoctrineRecord
{
    public function setTableDefinition()
    {
        $this->setTableName('farm_animal');
        $this->hasColumn('name', 'string', 20, array(
             'type' => 'string',
             'length' => '20',
             ));
        $this->hasColumn('sound', 'string', 20, array(
             'type' => 'string',
             'length' => '20',
             ));
        $this->hasColumn('leg_count', 'integer', null, array(
             'type' => 'integer',
             ));
        $this->hasColumn('created_at', 'timestamp', null, array(
             'type' => 'timestamp',
             ));
        $this->hasColumn('updated_at', 'timestamp', null, array(
             'type' => 'timestamp',
             ));
    }   
 
    public function setUp()
    {
        parent::setUp();
 
    }
}

Doctrine’s “concrete inheritance” looks pretty solid. Let’s take a look at the third and final style, “column aggregation.”

Column Aggregation

To be honest, I’m pretty baffled as to what this one is supposed to do for us. Hopefully this example will make things clearer.

FarmAnimal:
  columns:
    name: string(20)
    sound: string(20)
    leg_count: integer
    created_at: timestamp
    updated_at: timestamp
 
Cow:
  inheritance:
    extends: FarmAnimal
    type: column_aggregation
    keyField: type
    keyValue: 1
  columns:
    purpose:
      type: enum
      values: [beef, dairy]
 
Dog:
  inheritance:
    extends: FarmAnimal
    type: column_aggregation
    keyField: type
    keyValue: 2
  columns:
    breed: string(20)
 
Chicken:
  inheritance:
    extends: FarmAnimal
    type: column_aggregation
    keyField: type
    keyValue: 3
  columns:
    egg_color:
      type: enum
      values: [brown, white]
$ symfony doctrine:build-model
$ symfony doctrine:build-sql
$ symfony doctrine:insert-sql
mysql> SHOW TABLES;
+-----------------------+
| Tables_in_inheritance |
+-----------------------+
| farm_animal           |
+-----------------------+
1 row IN SET (0.00 sec)
 
mysql> DESCRIBE farm_animal;
+------------+-----------------------+------+-----+---------+----------------+
| FIELD      | Type                  | NULL | KEY | DEFAULT | Extra          |
+------------+-----------------------+------+-----+---------+----------------+
| id         | bigint(20)            | NO   | PRI | NULL    | AUTO_INCREMENT |
| name       | varchar(20)           | YES  |     | NULL    |                |
| sound      | varchar(20)           | YES  |     | NULL    |                |
| leg_count  | bigint(20)            | YES  |     | NULL    |                |
| created_at | datetime              | YES  |     | NULL    |                |
| updated_at | datetime              | YES  |     | NULL    |                |
| type       | varchar(255)          | YES  |     | NULL    |                |
| purpose    | enum('beef','dairy')  | YES  |     | NULL    |                |
| breed      | varchar(20)           | YES  |     | NULL    |                |
| egg_color  | enum('brown','white') | YES  |     | NULL    |                |
+------------+-----------------------+------+-----+---------+----------------+
10 rows IN SET (0.01 sec)

One table again, just like simple inheritance. Since I already rejected this structure, I see no reason to continue with column aggregation.

Conclusion

In my opinion, Doctrine’s simple inheritance and column aggregation are invalid and concrete is the only way to go. I hope these examples cleared up some confusion for anyone who had as much trouble with these concepts as I did.