Jun
10
2010

Per-environment fixtures in symfony

UPDATE:

I have now packaged this script into a symfony plugin called sfEnvironmentFixturesPlugin. Check it out and hit the ‘I use it’ link if you find it useful!

Feel free to leave questions, comments, and bug reports in the comments.

Symfony‘s fixtures are a great way to set up initial data and/or test data for a system. Whenever I create a new symfony project however, I always find myself wishing there was an easy way to specify which fixtures contain data required for production systems (the ‘initial’ data) and which contain data that I only need when developing (the ‘test’ data).

I recently started such a project and came across a reasonably elegant solution. With a simple YAML file placed in your fixtures directory, you too can have such a set up if so desired.

The beauty of symfony’s fixtures (at least in the case of Doctrine, not sure about Propel) is that they are interpreted as PHP. This allows us to do nice things like generating large amounts of test data by using iteration instead of writing out each entry individually. It also allows us to exploit it with a small script that can pull in other fixture files.

And now for the script itself:

byEnv.yml:

<?php

$_currentEnv = sfApplicationConfiguration::getActive()->getEnvironment();
$_envFixturesDir = sfConfig::get('sf_data_dir').'/fixtures_'.$_currentEnv;

if (is_dir($_envFixturesDir))
{
  taskMessage("Loading data fixtures for environment \"$_currentEnv\"");
  taskMessage("Loading data fixtures from \"$_envFixturesDir\"");

  foreach (findFixtures($_envFixturesDir) as $_envFixture)
  {
    echo "\n";
    include($_envFixture);
    echo "\n";
  }
}

/**
* @return array
*/

function findFixtures($path)
{
  $fixtures = array();
 
  if (!is_dir($path)) return;

  foreach (scandir($path) as $fixture)
  {
    $subPath = "$path/$fixture";
   
    if (substr($fixture, 0, 1) == '.') continue;
   
    if (is_dir($subPath))
    {
      findFixtures($subPath);
      continue;
    }
   
    if (strtolower(substr($fixture, -4)) != '.yml') continue;
   
    $fixtures[] = $subPath;
  }
 
  return $fixtures;
}

function taskMessage($text)
{
  $maxLineSize = ctype_digit(trim(shell_exec('tput cols 2>&1'))) ? (integer) shell_exec('tput cols') : 78;
 
  sfApplicationConfiguration::getActive()->getEventDispatcher()->notify(
    new sfEvent(null, 'command.log', array('>> doctrine  '.messageExcerpt($text, $maxLineSize - 12)))
  );
}

/**
* @return string
*/

function messageExcerpt($text, $size = 66)
{
  if (strlen($text) < $size) return $text;

  $subsize = floor(($size - 3) / 2);

  return substr($text, 0, $subsize).'...'.substr($text, -$subsize);
}

Place this script in your fixtures directory, then create a fixtures_dev sub-directory in your project’s data directory. Any fixtures placed in this directory will be included when you run the relevant symfony task using the dev environment.

You can use this system for any environment, including custom ones. For example, to load the fixtures needed for a production environment you could run the following:

symfony doctrine:data-load --env=prod

To load initial data, and test data stored in the development fixtures folder:

symfony doctrine:data-load --env=dev

To load initial data, and special data for the foo environment:

symfony doctrine:data-load --env=foo

IMPORTANT!

Be VERY careful when using this in a production environment. The default environment for the doctrine:data-load task is dev. This means if you don’t specify an environment, all your test data will be loaded.

The script includes some nice features too. It will search through directories recursively, just like the original fixture loading system, so you can organise your fixtures how you please. The script will also output messages to the console to notify you of which directories have actually been processed.

Hopefully someone will find this as useful as I do. Enjoy!

8 Comments »

  • Omar Beltran says:

    Hi,

    Can you help me, I have the next error:

    Call to undefined function loadFixtures() in \data\fixtures\byEnv.yml on line 32

    • Erin says:

      Hi Omar

      Very sorry, there was a small typo in the code. Just change “loadFixtures” to “findFixtures” and it should work fine.

      I’ve updated the code above also.

  • Faizan says:

    Thanks, Nice effort really helpful!

  • Prasad says:

    wonderful! I’m speechless!

  • Christof says:

    You might want to remove this from your DNS:

    ezzatron.com. 86400 IN AAAA ::1

    For people using firefox and ipv6 dns, they will see their localhost instead of your page.

    :-)

  • Dan says:

    Hi, I think i have an issue with the plugin where it doesn’t retain the order files are loaded in cross directory.

    e.g
    data/fixtures
    –001_users.yml
    –003_data.yml
    data/fixtures_dev
    –002_site.yml

    If 003_data.yml references an entity in 002_site.yml the data load fails because the load attempts to load the files in an incorrect order. Would this be correct?

    • Erin says:

      I’m fairly sure that’s not the issue. I’ve just tried a bunch of similar scenarios and everything worked fine.

      If you can provide a simple set of fixtures that allow me to reproduce the issue, that would be very helpful :)

RSS feed for comments on this post. TrackBack URL


Leave a Reply