Blog

New in symfony 1.3: Project Creation Customization

Symfony Live 2010 Paris Conference

« Back to the Blog

Categories

Feeds

feed Posts feed

comments feed Comments feed

symfony training
Be trained by symfony experts
Feb 15: Paris (What's new in 1.3 / 1.4 - English)
Feb 15: Paris (and Zend Framework Together - English)
Feb 15: Paris (Hosting Practices with symfony - English)
Feb 24: Paris (1.4 + Doctrine - Français)
Mar 04: Online (What's new in 1.3/1.4 - Français)
and more...

Archives

Creative Commons License This work is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License.

Today, I'm really excited to announce a great new feature for the upcoming symfony 1.3 version: the ability to customize the project creation process. Let me explain why it is useful and how you can take advantage of this cool feature.

Customizing the generate:project Task

As you might know, symfony tasks are classes. As any other class, it is pretty easy to customize and extend the existing tasks; except for one of them: the generate:project task. That's because no project exists when you execute this task, and so there is no way to customize it... until now. The task now takes an --installer option, which is a PHP script that will be executed during the project creation process:

$ php /path/to/symfony generate:project --installer=/domewhere/fabien_installer.php

If you enable URL file-access for the include() function in your php.ini, you can even pass a URL as an installer (of course you need to be very careful when doing this with script you know nothing about):

 $ symfony generate:project
 --installer=http://example.com/sf_installer.php

This script is executed in the context of the sfGenerateProjectTask instance, so you have access to all its methods to do your job, and there is a bunch of them.

installDir()

The first useful method is installDir(). It allows you to copy a bunch of files in the newly created project. Let's say you want to add some files here and there in the default directory structure, create them under a skeleton directory and add the following code in your installer script:

$this->installDir(dirname(__FILE__).'/skeleton');

runTask()

You can also run another task with the runTask() method. It takes the task name, and a string representing the arguments and the options you want to pass to it:

$this->runTask('configure:author', "'Fabien Potencier'");

You can also pass the arguments and the options as arrays:

$this->runTask('configure:author', array('author' => 'Fabien Potencier'));

The task shortcut names also work as expected:

$this->runTask('cc');

You can of course install plugins:

$this->runTask('plugin:install', 'sfDoctrineGuardPlugin');

If you want to install a specific version of a plugin, just appends the needed options to the argument string:

$this->runTask('plugin:install', 'sfDoctrineGuardPlugin --release=10.0.0 --stability=beta');

If you need to execute a task from a freshly installed plugin, don't forget to reload the tasks:

$this->reloadTasks();

Loggers

As you are inside a task context, you can log things pretty easily:

// a simple log
$this->log('some installation message');
 
// log a block
$this->logBlock(array('', 'Fabien\'s Crazy Installer', ''), 'ERROR');
 
// log in a section
$this->logSection('install', 'install some crazy files');

You can ask a confirmation:

if (!$this->askConfirmation('Are you sure you want to run this crazy installer?'))
{
  $this->logSection('install', 'You made the right choice!');
 
  return;
}

You can also ask any question:

$secret = $this->ask('Give a unique string for the CSRF secret:');

Or ask a question and validate the answer:

$validator = new sfValidatorEmail(array(), array('invalid' => 'hmmm, it does not look like an email!'));
$email = $this->askAndValidate('Please, give me your email:', $validator);

Filesystem Operations

If you want to do filesystem changes, you can access the filesystem object like this:

$this->getFilesystem()->...();

It's just a PHP script

The installer script is just another PHP file. So, you can do pretty anything you want. Be creative!

Example Script

Here is an example that uses a lot of the possibilities described above:

<?php
 
$this->logBlock(array('', 'Fabien\'s Crazy Installer', ''), 'ERROR');
 
if (!$this->askConfirmation('Are you sure you want to run this crazy installer?'))
{
  $this->logSection('install', 'You made the right choice!');
 
  return;
}
 
$this->installDir(dirname(__FILE__).'/skeleton');
 
$this->runTask('plugin:publish-assets');
 
$validator = new sfValidatorEmail(array(), array('invalid' => 'hmmm, it does not look like an email!'));
$email = $this->askAndValidate('Please, give me your email:', $validator);
 
$this->runTask('configure:author', sprintf("'%s'", $email));
 
$secret = $this->ask('Give a unique string for the CSRF secret:');
 
$this->runTask('generate:app', 'frontend --escaping-strategy=true --csrf-secret='.$secret);
 
$this->runTask('plugin:install', 'sfDoctrineGuardPlugin');
$this->reloadTasks();
 
$this->runTask('guard:create-user', 'fabien SuperPassword');
 
$this->runTask('cache:clear');

The Sandbox Creation Process

You probably know the symfony sandbox. It's a pre-packaged symfony project with a ready-made application and a pre-configured SQLite database. It helps newcomers bypass the command-line altogether as they just have to download the archive and they are ready to go.

The sandbox is nothing more than a bunch a commands executed before the archive is created. Until now, this job was done by the data/bin/create_sandbox.sh script. As of symfony 1.3, it is just an installer script. So, you can create a project that is the same as the sandbox like this:

$ php symfony generate:project --installer=/path/to/symfony/data/bin/sandbox_installer.php

Is it useful to create a sandbox? Probably not. But you can have a look at the installer script as a good example of what can be done:

$this->installDir(dirname(__FILE__).'/sandbox_skeleton');
 
$this->logSection('install', 'add symfony CLI for Windows users');
$this->getFilesystem()->copy(dirname(__FILE__).'/symfony.bat', sfConfig::get('sf_root_dir').'/symfony.bat');
 
$this->logSection('install', 'add LICENSE');
$this->getFilesystem()->copy(dirname(__FILE__).'/../../LICENSE', sfConfig::get('sf_root_dir').'/LICENSE');
 
$this->logSection('install', 'default to sqlite');
$this->runTask('configure:database', sprintf("'sqlite:%s/sandbox.db'", sfConfig::get('sf_data_dir')));
 
$this->logSection('install', 'create an application');
$this->runTask('generate:app', 'frontend');
 
$this->logSection('install', 'publish assets');
$this->runTask('plugin:publish-assets');
 
$this->logSection('install', 'fix sqlite database permissions');
touch(sfConfig::get('sf_data_dir').'/sandbox.db');
chmod(sfConfig::get('sf_data_dir'), 0777);
chmod(sfConfig::get('sf_data_dir').'/sandbox.db', 0777);
 
$this->logSection('install', 'add an empty file in empty directories');
$seen = array();
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator(sfConfig::get('sf_root_dir')), RecursiveIteratorIterator::CHILD_FIRST) as $path => $item)
{
  if ($item->isDir() && !$item->isLink() && !isset($seen[$path]))
  {
    touch($item->getRealPath().'/.sf');
  }
 
  $seen[$item->getPath()] = true;
}

That's pretty much it...

Instead of running the same tasks again and again each time you create a new symfony project, you can now create your own installer script and tweak your symfony project installations the way you want.

I hope you will find useful usages for this new feature. If you create great installer scripts, please share them with the community (copy the URL in the comments).

Comments comments feed

The Sensio Labs Network

Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting.
Sensio Labs also supports several large Open-Source projects.