Master Symfony2 fundamentals
Blog
New in symfony 1.3: Project Creation Customization by Fabien Potencier – June 10, 2009 – 15 comments
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 yourphp.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
is a trademark of Fabien Potencier. All rights reserved.
Add a Comment
Comments
Thanks.
How would installDir work when the installer script is referenced via http://? It wouldn't presumably...?
Nice!
PS: I guess your 'domewhere' on the first snippet was a typo. :-)
I was doing custom setups using a script I created myself before, but this looks a heckuva lot more customizable and expandable.