- Tasks at a Glance
- Writing your own Tasks
- The Options System
- Accessing the Database
- Sending Emails
- Generating URLs
- Accessing the I18N System
- Refactoring your Tasks
- Executing a Task inside a Task
- Manipulating the Filesystem
- Using Skeletons to generate Files
- Using a dry-run Option
- Writing unit Tests
- Helper Methods: Logging
- Helper Methods: User Interaction
- Bonus Round: Using Tasks with a Crontab
- Bonus Round: Using STDIN
- Final Thoughts
by Geoffrey Bachelet
Symfony 1.1 introduced a modern, powerful, and flexible command line system in replacement of the old pake-based tasks system. From version to version, the tasks system has been improved to make it what it is today.
Many web developers won't see the added value in tasks. Often, those developers don't realize the power of the command line. In this chapter, we are going to dive into tasks, from the very beginning to more advanced usage, seeing how it can help your everyday work, and how you can get the best from tasks.
Tasks at a Glance
A task is a piece of code that is run from the command line using the symfony
php script at the root of your project. You may already have run into tasks
through the well-known cache:clear
task (also known as cc
) by running it in
a shell:
$ php symfony cc
Symfony provides a set of general purpose built-in tasks for a variety of uses.
You can get a list of the available tasks by running the symfony
script
without any arguments or options:
$ php symfony
The output will look something like this (content truncated):
Usage: symfony [options] task_name [arguments] Options: --help -H Display this help message. --quiet -q Do not log messages to standard output. --trace -t Turn on invoke/execute tracing, enable full backtrace. --version -V Display the program version. --color Forces ANSI color output. --xml To output help as XML Available tasks: :help Displays help for a task (h) :list Lists tasks app :routes Displays current routes for an application cache :clear Clears the cache (cc, clear-cache)
You may have already noticed that tasks are grouped. Groups of tasks are called
namespaces, and tasks name are generally composed of a namespace and a task name
(except for the help
and list
tasks that don't have a namespace). This
naming scheme allows for easy task categorization, and you should choose a
meaningful namespace for each of your tasks.
Writing your own Tasks
Getting started writing tasks with symfony takes only a few minutes. All
you have to do is create your task, name it, put some logic into it, and voilà,
you're ready to run your first custom task. Let's create a very simple Hello,
World! task, for example in lib/task/sayHelloTask.class.php
:
class sayHelloTask extends sfBaseTask { public function configure() { $this->namespace = 'say'; $this->name = 'hello'; } public function execute($arguments = array(), $options = array()) { echo 'Hello, World!'; } }
Now run it with the following command:
$ php symfony say:hello
This task will only output Hello, World!, but it's only a start! Tasks are
not really meant to output content directly through the echo
or print
statements. Extending sfBaseTask
allows us to use a handful of helpful methods,
including the log()
method, which does just what we want to do, output
content:
public function execute($arguments = array(), $options = array()) { $this->log('Hello, World!'); }
Since a single task call can result in multiple tasks outputting content, you
may actually want to use the logSection()
method:
public function execute($arguments = array(), $options = array()) { $this->logSection('say', 'Hello, World!'); }
Now, you might have already noticed the two arguments passed to the execute()
method, $arguments
and $options
. These are meant to hold all arguments and
options passed to your task at runtime. We will cover arguments and options
extensively later. For now, let's just add a bit of interactivity to our task
by allowing the user to specify who we want to say hello to:
public function configure() { $this->addArgument('who', sfCommandArgument::OPTIONAL, 'Who to say hello to?', 'World'); } public function execute($arguments = array(), $options = array()) { $this->logSection('say', 'Hello, '.$arguments['who'].'!'); }
Now the following command:
$ php symfony say:hello Geoffrey
Should produce the following output:
>> say Hello, Geoffrey!
Wow, that was easy.
By the way, you might want to include a little more metadata in the tasks, like
what it does for example. You can do so by setting the briefDescription
and
detailedDescription
properties:
public function configure() { $this->namespace = 'say'; $this->name = 'hello'; $this->briefDescription = 'Simple hello world'; $this->detailedDescription = <<<EOF The [say:hello|INFO] task is an implementation of the classical Hello World example using symfony's task system. [./symfony say:hello|INFO] Use this task to greet yourself, or somebody else using the [--who|COMMENT] argument. EOF; $this->addArgument('who', sfCommandArgument::OPTIONAL, 'Who to say hello to?', 'World'); }
As you can see, you can use a basic set of markup to decorate your description. You can check the rendering using symfony's task help system:
$ php symfony help say:hello
The Options System
Options in a symfony task are organized into two distinct sets, options and arguments.
Options
Options are those that you pass using hyphens. You can add them to your command line in any order. They can either have a value or not, in which case they act as a boolean. More often than not, options have both a long and short form. The long form is usually invoked using two hyphens while the short form requires only one hyphen.
Examples of common options are the help switch (--help
or -h
), the
verbosity switch (--quiet
or -q
) or the version switch (--version
or
-V
).
note
Options are defined with an sfCommandOption
class and stored in an
sfCommandOptionSet
class.
Arguments
Arguments are just a piece of data that you append to your command line. They must be specified in the same order in which they were defined, and you must enclose them in quotes if you want to include a space in them (or you could also escape the spaces). They can be either required or optional, in which case you should specify a default value in the argument's definition.
note
Obviously, arguments are defined with an sfCommandArgument
class and stored in an
sfCommandArgumentSet
class.
Default Sets
Every symfony task comes with a set of default options and arguments:
--help
(-H
): Displays this help message.--quiet
(-q
): Do not log messages to standard output.--trace
(-t
): Turns on invoke/execute tracing, enable full backtrace.--version
(-V
): Displays the program version.--color
: Forces ANSI color output.
Special Options
Symfony's task system understands two very special options, application
and
env
.
The application
option is needed when you want access to an
sfApplicationConfiguration
instance rather than just an sfProjectConfiguration
.
instance. This is the case, for example, when you want to generate URLs, since
routing is generally associated to a specific application.
When an application
option is passed to a task, symfony will automatically
detect it and create the corresponding sfApplicationConfiguration
object instead of
the default sfProjectConfiguration
object. Note that you can set a default value for
this option, hence saving you the hassle of having to pass an application by
hand each time you run the task.
The env
option controls, obviously, the environment in which the task
executes. When no environment is passed, test
is used by default. Just like
for application
, you can set a default value for the env
option that will
automatically be used by symfony.
Since application
and env
are not included in the default options set, you
have to add them by hand in your task:
public function configure() { $this->addOptions(array( new sfCommandOption('application', null, sfCommandOption::PARAMETER_REQUIRED, 'The application name', 'frontend'), new sfCommandOption('env', null, sfCommandOption::PARAMETER_REQUIRED, 'The environment', 'dev'), )); }
In this example, the frontend
application will be automatically used, and
unless a different environment is specified, the task will run in the dev
environment.
Accessing the Database
Having access to your database from inside a symfony task is just a matter of
instantiating an sfDatabaseManager
instance:
public function execute($arguments = array(), $options = array()) { $databaseManager = new sfDatabaseManager($this->configuration); }
You can also access the ORM's connection object directly:
public function execute($arguments = array(), $options = array()) { $databaseManager = new sfDatabaseManager($this->configuration); $connection = $databaseManager->getDatabase()->getConnection(); }
But what if you have several connections defined in your databases.yml
? You
could, for example, add a connection
option to your task:
public function configure() { $this->addOption('connection', sfCommandOption::PARAMETER_REQUIRED, 'The connection name', 'doctrine'); } public function execute($arguments = array(), $options = array()) { $databaseManager = new sfDatabaseManager($this->configuration); $connection = $databaseManager->getDatabase(isset($options['connection']) ? $options['connection'] : null)->getConnection(); }
As usual, you can set a default value for this option.
Voilà! You can now manipulate your models just as if you were in your symfony application.
note
Be careful when batch processing using your favorite ORM's objects. Both Propel and Doctrine suffer from a well known PHP bug related to cyclic references and the garbage collector that results in a memory leak. This has been partially fixed in PHP 5.3.
Sending Emails
One of the most common use for tasks is sending emails. Until symfony 1.3, sending email was not really straightforward. But times have changed: symfony now features full integration with Swift Mailer, a feature-rich PHP mailer library, so let's use it!
Symfony's task system exposes the mailer object through the
sfCommandApplicationTask::getMailer()
method. That way, you can gain access
to the mailer and easily send emails:
public function execute($arguments = array(), $options = array()) { $mailer = $this->getMailer(); $mailer->composeAndSend($from, $recipient, $subject, $messageBody); }
note
Since the mailer's configuration is read from the application configuration, your task must accept an application option in order to be able to use the mailer.
note
If you are using the spool strategy, emails are only sent when you call
the project:send-emails
task.
In most cases, you won't have your message's content sitting in a magical
$messageBody
variable just waiting to be sent, you'll want to somehow
generate it. There is no preferred way in symfony to generate content for your
emails, but there are a couple tips you can follow to make your life easier:
Delegate Content Generation
For example, create a protected method for your task that returns the content for the email you're sending:
public function execute($arguments = array(), $options = array()) { $this->getMailer()->composeAndsend($from, $recipient, $subject, $this->getMessageBody()); } protected function getMessageBody() { return 'Hello, World'; }
Use Swift Mailer's Decorator Plugin
Swift Mailer features a plugin known as
Decorator
that is basically a
very simple, yet efficient, template engine that can take recipient-specific
replacement value-pairs and apply them throughout all mails being sent.
See Swift Mailer's documentation for more information.
Use an external Templating Library
Integrating a third party templating library is easy. For example, you could
use the brand new templating component released as part of the Symfony
Components project. Just drop the component code somewhere in your project
(lib/vendor/templating/
would be a good place), and put down the following
code in your task:
protected function getMessageBody($template, $vars = array()) { $engine = $this->getTemplateEngine(); return $engine->render($template, $vars); } protected function getTemplateEngine() { if (is_null($this->templateEngine)) { $loader = new sfTemplateLoaderFilesystem(sfConfig::get('sf_app_dir').'/templates/emails/%s.php'); $this->templateEngine = new sfTemplateEngine($loader); } return $this->templateEngine; }
Getting the best of both Worlds
There's still more that you can do. Swift Mailer's Decorator
plugin is very handy
since it can manage replacements on a recipient-specific basis. It means that
you define a set of replacements for each of your recipients, and Swift Mailer
takes care of replacing tokens with the right value based on the recipient
of the mail being sent. Let's see how we can integrate this with the templating
component:
public function execute($arguments = array(), $options = array()) { $message = Swift_Message::newInstance(); // fetches a list of users foreach($users as $user) { $replacements[$user->getEmail()] = array( '{username}' => $user->getEmail(), '{specific_data}' => $user->getSomeUserSpecificData(), ); $message->addTo($user->getEmail()); } $this->registerDecorator($replacements); $message ->setSubject('User specific data for {username}!') ->setBody($this->getMessageBody('user_specific_data')); $this->getMailer()->send($message); } protected function registerDecorator($replacements) { $this->getMailer()->registerPlugin(new Swift_Plugins_DecoratorPlugin($replacements)); } protected function getMessageBody($template, $vars = array()) { $engine = $this->getTemplateEngine(); return $engine->render($template, $vars); } protected function getTemplateEngine($replacements = array()) { if (is_null($this->templateEngine)) { $loader = new sfTemplateLoaderFilesystem(sfConfig::get('sf_app_template_dir').'/emails/%s.php'); $this->templateEngine = new sfTemplateEngine($loader); } return $this->templateEngine; }
With apps/frontend/templates/emails/user_specific_data.php
containing the
following code:
Hi {username}! We just wanted to let you know your specific data: {specific_data}
And that's it! You now have a fully featured template engine to build your email content.
Generating URLs
Writing emails usually requires that you generate URLs based on your routing
configuration. Fortunately enough, generating URLs has been made easy in
symfony 1.3 since you can directly access the routing of the current
application from inside a task by using the sfCommandApplicationTask::getRouting()
method:
public function execute($arguments = array(), $options = array()) { $routing = $this->getRouting(); }
note
Since the routing is application dependent, you have to make sure that your application has an application configuration available, otherwise you won't be able to generate URLs using the routing.
See the Special Options section to learn how to automatically get an application configuration in your task.
Now that we have a routing instance, it's quite straightforward to generate a
URL using the generate()
method:
public function execute($arguments = array(), $options = array()) { $url = $this->getRouting()->generate('default', array('module' => 'foo', 'action' => 'bar')); }
The first argument is the route's name and the second is an array of parameters for the
route. At this point, we have generated a relative URL, which is most likely
not what we want. Unfortunately, generating absolute URLs in a task will not work
since we don't have an sfWebRequest
object to rely on to fetch the HTTP host.
One simple way to solve this is to set the HTTP host in your
factories.yml
configuration file:
all: routing: class: sfPatternRouting param: generate_shortest_url: true extra_parameters_as_query_string: true context: host: example.org
See the context_host
setting? This is what the routing will use when asked
for an absolute URL:
public function execute($arguments = array(), $options = array()) { $url = $this->getRouting()->generate('my_route', array(), true); }
Accessing the I18N System
Not all factories are as easily accessible as the mailer and the routing.
Should you need access to one of them, it's really not too hard to instantiate
them. For example, say you want to internationalize your tasks, you would then
want to access symfony's i18n subsystem. This is easily done using the
sfFactoryConfigHandler
:
protected function getI18N($culture = 'en') { if (!$this->i18n) { $config = sfFactoryConfigHandler::getConfiguration($this->configuration->getConfigPaths('config/factories.yml')); $class = $config['i18n']['class']; $this->i18n = new $class($this->configuration, null, $config['i18n']['param']); } $this->i18n->setCulture($culture); return $this->i18n; }
Let's see what's going on here. First, we are using a simple caching
technique to avoid re-building the i18n component at each call. Then, using the
sfFactoryConfigHandler
, we retrieve the component's configuration in order to
instantiate it. We finish by setting the culture configuration. The task now
has access to internationalization:
public function execute($arguments = array(), $options = array()) { $this->log($this->getI18N('fr')->__('some translated text!')); }
Of course, always passing the culture is not very handy, especially if you don't need to change culture very often in your task. We will see how to arrange that in the next section.
Refactoring your Tasks
Since sending emails (and generating content for them) and generating URLs are
two very common task, it may be a good idea to create a base task that
provides these two features automatically for each task. This is fairly easy to
do. Create a base class inside your project, for example
lib/task/sfBaseEmailTask.class.php
.
class sfBaseEmailTask extends sfBaseTask { protected function registerDecorator($replacements) { $this->getMailer()->registerPlugin(new Swift_Plugins_DecoratorPlugin($replacements)); } protected function getMessageBody($template, $vars = array()) { $engine = $this->getTemplateEngine(); return $engine->render($template, $vars); } protected function getTemplateEngine($replacements = array()) { if (is_null($this->templateEngine)) { $loader = new sfTemplateLoaderFilesystem(sfConfig::get('sf_app_template_dir').'/templates/emails/%s.php'); $this->templateEngine = new sfTemplateEngine($loader); } return $this->templateEngine; } }
While we're at it, we are going to automate the task's options setup. Add these
methods to the sfBaseEmailTask
class:
public function configure() { $this->addOption('application', null, sfCommandOption::PARAMETER_REQUIRED, 'The application', 'frontend'); } protected function generateUrl($route, $params = array()) { return $this->getRouting()->generate($route, $params, true); }
We use the configure()
method to add common options to all extending tasks.
Unfortunately, any class extending sfBaseEmailTask
will now have to call
parent::configure
in its own configure()
method, but that's really a minor
annoyance in regard of added value.
Now let's refactor the I18N access code from the previous section:
public function configure() { $this->addOption('application', null, sfCommandOption::PARAMETER_REQUIRED, 'The application', 'frontend'); $this->addOption('culture', null, sfCommandOption::PARAMETER_REQUIRED, 'The culture', 'en'); } protected function getI18N() { if (!$this->i18n) { $config = sfFactoryConfigHandler::getConfiguration($this->configuration->getConfigPaths('config/factories.yml')); $class = $config['i18n']['class']; $this->i18n = new $class($this->configuration, null, $config['i18n']['param']); $this->i18n->setCulture($this->commandManager->getOptionValue('culture')); } return $this->i18n; } protected function changeCulture($culture) { $this->getI18N()->setCulture($culture); } protected function process(sfCommandManager $commandManager, $options) { parent::process($commandManager, $options); $this->commandManager = $commandManager; }
We have a problem to solve here: it is not possible to access arguments and
options values outside execute()
's scope. To fix that, we are simply
overloading the process()
method to attach the options manager to the class.
The options manager is, as its name says, managing arguments and options for
the current task. For example, you can access options values via the
getOptionValue()
method.
Executing a Task inside a Task
An alternative way to refactor your tasks is to embed a task inside another
task. This is made particularly easy through the
sfCommandApplicationTask::createTask()
and
sfCommandApplicationTask::runTask()
methods.
The createTask()
method will create an instance of a task for you. Just pass it
a task name, just as if you were on the command line, and it will return you an
instance of the desired task, ready to be run:
$task = $this->createTask('cache:clear'); $task->run();
But since we are lazy, the runTask
does everything for us:
$this->runTask('cache:clear');
Of course, you can pass arguments and options (in this order):
$this->runTask('plugin:install', array('sfGuardPlugin'), array('install_deps' => true));
Embedding tasks is useful for composing powerful tasks from more simple tasks.
For example, you could combine several tasks in a project:clean
task that you
would run after each deployment:
$tasks = array( 'cache:clear', 'project:permissions', 'log:rotate', 'plugin:publish-assets', 'doctrine:build-model', 'doctrine:build-forms', 'doctrine:build-filters', 'project:optimize', 'project:enable', ); foreach($tasks as $task) { $this->run($task); }
Manipulating the Filesystem
Symfony comes with a built-in simple filesystem abstraction (sfFilesystem
)
that permits the execution of simple operations on files and directories. It is
accessible inside a task with $this->getFilesystem()
. This abstraction
exposes the following methods:
sfFilesystem::copy()
, to copy a filesfFilesystem::mkdirs()
, creates recursive directoriessfFilesystem::touch()
, to create a filesfFilesystem::remove()
, to delete a file or directorysfFilesystem::chmod()
, to change permissions on a file or directorysfFilesystem::rename()
, to rename a file or directorysfFilesystem::symlink()
, to create a link to a directorysfFilesystem::relativeSymlink()
, to create a relative link to a directorysfFilesystem::mirror()
, to mirror a complete file treesfFilesystem::execute()
, to execute an arbitrary shell command
It also exposes a very handy method that we are going to cover in the next
section: replaceTokens()
.
Using Skeletons to generate Files
Another common use for tasks is to generate files. Generating files can be made
easy using skeletons and the aforementioned method
sfFilesystem::replaceTokens()
. As its name suggests, this methods replaces
tokens inside a set of files. That is, you pass it an array of file, a list of
tokens and it replaces every occurrence of each token with its
assigned value, for each file in the array.
To better understand how this is useful, we are going to partially rewrite an
existing task: generate:module
. For the sake of clarity and brevity, we will
only look at the execute
part of this task, assuming it has been configured
properly with all needed options. We will also skip validation.
Even before starting to write the task, we need to create a skeleton for the
directories and files we are going to create, and store it somewhere like
data/skeleton/
:
data/skeleton/ module/ actions/ actions.class.php templates/
The actions.class.php
skeleton could look like something like this:
class %moduleName%Actions extends %baseActionsClass% { }
The first step of our task will be to mirror the file tree at the right place:
$moduleDir = sfConfig::get('sf_app_module_dir').$options['module']; $finder = sfFinder::type('any'); $this->getFilesystem()->mirror(sfConfig::get('sf_data_dir').'/skeleton/module', $moduleDir, $finder);
Now let's replace the tokens in actions.class.php
:
$tokens = array( 'moduleName' => $options['module'], 'baseActionsClass' => $options['base-class'], ); $finder = sfFinder::type('file'); $this->getFilesystem()->replaceTokens($finder->in($moduleDir), '%', '%', $tokens);
And that's it, we generated our new module, using token replacing to customize it.
note
The built-in generate:module
actually looks into data/skeleton/
for
alternative skeleton to use instead of the default ones, so watch your step!
Using a dry-run Option
Often you want to be able to preview the result of a task before actually running it. Here are a couple of tips on how to do so.
First, you should use a standard name, such as dry-run
. Everyone will
recognize this for what it is. Until symfony 1.3, sfCommandApplication
did
add a default dry-run
option, but now it should be added by hand (possibly in
a base class, as demonstrated above):
$this->addOption(new sfCommandOption('dry-run', null, sfCommandOption::PARAMETER_NONE, 'Executes a dry run');
You would then invoke your task like this:
./symfony my:task --dry-run
The dry-run
option indicates that the task should not make any change.
Should not make any change, remember this, they are the key words. When
running in dry-run
mode, your task must leave the environment exactly as it
was before, including (but not limited to):
- The database: do not insert, update or delete records from your tables. You can use a transaction to achieve this.
- The filesystem: do not create, modify or delete files from your filesystem.
- Email sending: do not send emails, or send them to a debug address.
Here is a simple example of using the dry-run
option:
$connection->beginTransaction(); // modify your database if ($options['dry-run']) { $connection->rollBack(); } else { $connection->commit(); }
Writing unit Tests
Since tasks can achieve a variety of goals, unit testing them is not an easy thing. As such, there's not one way to test tasks, but there are some principles to follow that can help make your tasks more testable.
First, think of your task like a controller. Remember the rule about controller? Thin controllers, fat models. That is, move all the business logic inside your models, that way, you can test your models instead of the task, which is way easier.
Once you think you can't get more logic into models, split your execute()
method into chunks of easily testable code, each residing in its own easily
accessible (read: public) method. Splitting your code has several benefits:
- it makes your task's
execute
more readable - it makes your task more testable
- it makes your task more extendable
Be creative, don't hesitate to build a small specific environment for your testing needs. And if you can't find any way to test that awesome task that you just wrote, there are two possibilities: either you wrote it bad or you should ask someone for his opinion. Also, you can always dig into someone else's code to see how they test things (symfony's tasks are well tested for example, even generators).
Helper Methods: Logging
Symfony's task system tries hard to make the developer's day easier, providing handy helper method for common operations such as logging and user interaction.
One can easily log messages to STDOUT
using the log
family of methods:
log
, accepts an array of messageslogSection
, a bit more elaborate, formats your message with a prefix (first argument) and a message type (fourth argument). When you log something too long, like a file path,logSection
will usually shrink your message, which can prove annoying. Use the third argument to specify a message max size that fits your messagelogBlock
, is the logging style used for exceptions. Here again, you can pass a formatting style
Available logging formats are ERROR
, INFO
, COMMENT
and QUESTION
. Don't
hesitate to try them to see what they look like.
Example usage:
$this->logSection('file+', $aVeryLongFileName, $this->strlen($aVeryLongFileName)); $this->logBlock('Congratulations! You ran the task successfuly!', 'INFO');
Helper Methods: User Interaction
Three more helpers are provided to ease user interaction:
ask()
, basically prints a question and returns any user inputaskConfirmation()
, we ask the user for a confirmation, allowingy
(yes) andn
(no) as user inputaskAndValidate()
, a very useful method that prints a question and validates the user input through ansfValidator
passed as the second argument. The third argument is an array of options in which you can pass a default value (value
), a maximum number of attempts (attempts
) and a formatting style (style
).
For example, you can ask a user for his email address and validate it on the fly:
$email = $this->askAndValidate('What is your email address?', new sfValidatorEmail());
Bonus Round: Using Tasks with a Crontab
Most UNIX and GNU/Linux systems allows for task planning through a
mechanism known as cron. The cron checks a configuration file (a crontab)
for commands to run at a certain time. Symfony tasks can easily be integrated
into a crontab, and the project:send-emails
task is a perfect candidate for
an example of that:
MAILTO="you@example.org" 0 3 * * * /usr/bin/php /var/www/yourproject/symfony project:send-emails
This configuration tells cron to run the project:send-emails
every day at
3am and to send all possible output (that is, logs, errors, etc) to the address
you@example.org.
note
For more information on the crontab configuration file format, type man 5
crontab
in a terminal.
You can, and should actually, pass arguments and options:
MAILTO="you@example.org" 0 3 * * * /usr/bin/php /var/www/yourproject/symfony project:send-emails --env=prod --application=frontend
note
You should replace /usr/bin/php
with the location of your PHP CLI binary.
If you don't have this information, you can try which php
on linux systems
or whereis php
on most other UNIX systems.
Bonus Round: Using STDIN
Since tasks are run in a command line environment, you can access the standard
input stream (STDIN). The UNIX command line allows applications to interact
between each other by a variety of means, one of which is the pipe,
symbolized by the character |. The pipe allows you to pass an application's
output (know as STDOUT) to another application's standard input (known as
STDIN). These are made accessible in your tasks through PHP's special
constants STDIN
and STDOUT
. There's also a third standard stream, STDERR,
accessible through STDERR
, meant to carry an applications' error messages.
So what can we do exactly with the standard input? Well, imagine you have an application running on your server that would like to communicate with your symfony application. You could of course have it communicate through HTTP, but a more efficient way would be to pipe its output to a symfony task. Say the application can send structured data (for example a PHP array serialization) describing domain objects that you want to include into your database. You could write the following task:
while ($content = trim(fgets(STDIN))) { if ($data = unserialize($content) !== false) { $object = new Object(); $object->fromArray($data); $object->save(); } }
You would then use it like this:
/usr/bin/data_provider | ./symfony data:import
data_provider
being the application providing new domain objects, and
data:import
being the task you just wrote.
Final Thoughts
What tasks can achieve is limited only by your imagination. Symfony's task system is powerful and flexible enough that you can do merely anything you can think off. Add to that the power of an UNIX shell, and you are really going to love tasks.
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.