New in symfony 1.3: Project Creation Customization
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).
Help the Symfony project!
As with any Open-Source project, contributing code or documentation is the most common way to help, but we also have a wide range of sponsoring opportunities.
Comments
Comments are closed.
To ensure that comments stay relevant, they are closed for old posts.
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.