Sfruttare la potenza della linea di comando
di Geoffrey Bachelet
Symfony 1.1 ha introdotto un moderno, potente e flessibile sistema da linea di comando in sostituzione del vecchio sistema dei task basato su pake. Di versione in versione, il sistema dei task è stato migliorato fino ad arrivare a ciò che è oggi.
Molti sviluppatori web non vedono il valore aggiunto dei task. Spesso, questi sviluppatori non comprendono la potenza della linea di comando. In questo capitolo approfondiremo l'argomento task, dalle basi all'uso più avanzato, osservando come possono aiutare il lavoro di tutti i giorni e come ottenere il massimo dai task.
Task al primo sguardo
Un task è una porzione di codice eseguito dalla linea di comando utilizzando lo script symfony
nella cartella radice del progetto. Avrete di sicuro già incontrato i task attraverso il ben famoso cache:clear
(conosciuto anche come cc
) lanciandolo nel terminale:
$ php symfony cc
Symfony mette a disposizione un insieme di task preconfezionati e di uso generico per molti scopi. Utilizzando lo script symfony
senza nessun parametro o opzione si ottiene la lista dei task disponibili:
$ php symfony
L'output avrà un aspetto simile al seguente (contenuto troncato):
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)
Forse avrete già notato che i task sono raggruppati. Gruppi di task sono chiamati namespace e i nomi dei task sono generalmente composti da un namespace e da un nome del task (eccezion fatta per i task help
e list
che non hanno un namespace). Questo schema di denominazione permette una semplice categorizzazione dei task ed è consigliato scegliere oculatamente i namespace per ognuno dei propri task.
Scrivere un proprio task
Iniziare a scrivere task con symfony è questione di pochi minuti. Tutto ciò che c'è da fare è creare il task, dargli un nome, aggiungerci della logica, e voilà: siete pronti a eseguire il vostro primo task personalizzato. Creiamo un semplicissimo task Hello, World!, per esempio 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!'; } }
Ora va eseguito con il seguente comando:
$ php symfony say:hello
Questo task si limita a mostrare Hello, World!, ma è solo l'inizio! I task non sono propriamente pensati per fornire dell'output direttamente attraverso i comandi echo
o print
. Estendendo sfBaseTask
è possibile utilizzare una moltitudine di utili metodi, incluso il metodo log()
che fa esattamente quello che vogliamo, output di contenuti:
public function execute($arguments = array(), $options = array()) { $this->log('Hello, World!'); }
Dato che l'esecuzione di un singolo task può tramutarsi in diversi task che producono contenuto in output, è preferibile utilizzare il metodo logSection()
:
public function execute($arguments = array(), $options = array()) { $this->logSection('say', 'Hello, World!'); }
Ora, probabilmente avrete già notato i due parametri passati al metodo execute()
, $arguments
e $options
. Questi si occupano di contenere tutti i parametri e le opzioni passate al task al momento dell'esecuzione. Ci occuperemo di parametri e di opzioni in modo approfondito in seguito. Per adesso aggiungiamo un po' di interattività al task permettendo all'utente di specificare chi vogliamo salutare:
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'].'!'); }
A questo punto il seguente comando:
$ php symfony say:hello Geoffrey
Dovrebbe produrre il seguente risultato:
>> say Hello, Geoffrey!
Wow, è stato facile.
A proposito, potreste voler inserire ancora un po' di metadati nel task come per esempio ciò di cui si occupa. Per fare questo basta impostare le proprietà briefDescription
e description
:
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'); }
Come potete vedere è possibile utilizzare un semplice insieme di markup per decorare la descrizione inserita. Per verificare la visualizzazione utilizzare il sistema di aiuto per i task di symfony:
$ php symfony help say:hello
Il sistema delle opzioni
Le opzioni nei task di symfony sono organizzate in due insiemi distinti, opzioni e parametri.
Opzioni
Le opzioni sono quelle passate utilizzando il trattino. È possibile aggiungerle alla linea di comando in qualsiasi ordine. Possono avere o meno un valore, nel secondo caso si comportano come booleani. Molto spesso le opzioni hanno entrambe i formati lungo e corto. Il formato lungo è solitamente invocato utilizzando due trattini mente quello corto ne richiede uno solo.
Esempio di opzioni comuni sono il selettore dell'help (--help
or -h
), il selettore della verbosità (--quiet
or -q
) o quello della versione (--version
or -V
).
Le opzioni sono definite con una classe
sfCommandOption
e memorizzare in una classesfCommandOptionSet
.
Argomenti
I parametri sono porzioni di dati che vengono aggiunte alla riga di comando. Devono essere specificati nello stesso ordine con il quale sono stati definiti, vanno racchiusi tra apici se si ha la necessità di includere uno spazio in essi (oppure va eseguito l'escape degli spazi). I parametri possono essere obbligatori o opzionali, nel qual caso sarà necessario specificare un valore di default nella definizione del parametro.
Ovviamente i parametri sono definiti nella classe
sfCommandArgument
e memorizzati nella classesfCommandArgumentSet
.
Impostazioni di default
Ogni task di symfony ha un insieme di opzioni e parametri di default:
--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.
Opzioni speciali
Il sistema dei task di symfony comprende due opzioni molto speciali quali application
e env
. L'opzione application
è necessaria quando di ha bisogno di accedere a un'istanza di sfApplicationConfiguration
oltre che al solo sfProjectConfiguration
. Questo è il caso, per esempio, di quando si vogliono generare URLs dato che il routing è generalmente associato a una specifica applicazione.
Quando un'opzione application
viene passata a un task, symfony la riconosce automaticamente e crea l'oggetto sfApplicationConfiguration
corrispondente invece che l'oggetto di default sfProjectConfiguration
. Ricordate che è possibile impostare un valore di default per questa opzione, salvandovi quindi dal dover passare manualmente l'applicazione corrispondente ogni volta che il task viene eseguito.
L'opzione env
controlla, ovviamente, l'ambiente nel quale il task viene eseguito. Quando non viene specificato l'ambiente, test
viene utilizzato come default. Come per l'opzione application
, è possibile impostare un valore di default per l'opzione env
che verrà utilizzato automaticamente da symfony.
Dato che application
e env
non sono incluse nell'insieme delle opzioni di default è necessario aggiungerle a mano nel 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 questo esempio l'applicazione frontend
verrà usata automaticamente e, a meno di non specificare un ambiente, il task verrà eseguito nell'ambiente dev
.
Accedere al database
Accedere al database direttamente da un task di symfony è solo questione di istanziare sfDatabaseManager
:
public function execute($arguments = array(), $options = array()) { $databaseManager = new sfDatabaseManager($this->configuration); }
È anche possibile accedere all'oggetto di connessione dell'ORM in modo diretto:
public function execute($arguments = array(), $options = array()) { $databaseManager = new sfDatabaseManager($this->configuration); $connection = $databaseManager->getDatabase()->getConnection(); }
Ma cosa succede quando si hanno diverse connessione definite del file databases.yml
? È possibile, per esempio, aggiungere un'opzione connection
al 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(); }
Come al solito è possibile impostare un valore di default per questa opzione.
Voilà! Ora è possibile manipolare i propri modelli proprio come se si stesse lavorando all'interno di un'applicazione symfony.
Fate molta attenzione quando eseguite processi batch utilizzando gli oggetti del vostro ORM preferito. Sia Propel che Doctrine soffrono di un ben noto bug di PHP legato alle referenze cicliche e al garbage collector che si manifestano con una perdita di memoria. Questo è stato parzialmente risolto in PHP 5.3.
Inviare email
Uno degli utilizzi più comuni dei task è l'invio di email. Prima di symfony 1.3 inviare email non era un processo così immediato. Ma i tempi sono cambiati: symfony ora propone la completa integraziene di Swift Mailer, una libreria PHP molto completa per l'invio delle email. Usiamola quindi!
Il sistema dei task di symfony espone l'oggetto mailer attraverso il metodo sfCommandApplicationTask::getMailer()
. In questo modo è possibile ottenere l'accesso al mailer e inviare email facilmente:
public function execute($arguments = array(), $options = array()) { $mailer = $this->getMailer(); $mailer->composeAndSend($from, $recipient, $subject, $messageBody); }
Visto che la configurazione del mailer viene letta dalla configurazione dell'applicazione, il task deve accettare un'opzione per l'applicazione per essere in grado di utilizzare il mailer.
Se utilizzate la strategia dello spool, le email vengono inviate solamente quando viene eseguito il task
project:send-emails
.
In molti casi non vorrete avere il contenuto del messaggio in una variabile $messageBody
solo in attesa di essere inviato, vorrete generarlo in qualche modo. Non esiste un modo preferibile in symfony per generare il contenuto delle proprie email, tuttavia abbiamo un paio di suggerimenti da seguire per avere una vita più facile:
Delegare la generazione del contenuto
Per esempio, creare un metodo protetto per il task che restituisce il contenuto per la mail che si sta per inviare:
public function execute($arguments = array(), $options = array()) { $this->getMailer()->composeAndsend($from, $recipient, $subject, $this->getMessageBody()); } protected function getMessageBody() { return 'Hello, World'; }
Utilizzare il decorator plugin di Swift Mailer
Swift Mailer mette a disposizione un plugin conosciuto come Decorator
, che non è altro che un semplicissimo, quanto efficiente, motore di template, che può prendere delle coppie di valori recipiente-rimpiazzo da utilizzare e applicarli a tutte le email da inviare.
Leggere la documentazione di Swift Mailer per ulteriori informazioni.
Utilizzare una libreria di template esterna
Integrare una libreria di template di terze parti è semplice. Per esempio si potrebbe utilizzare il nuovissimo templating component rilasciato come parte del progetto Symfony Components. Basta copiare il codice del component da qualche parte nel progetto (lib/vendor/templating/
potrebbe essere un buon posto) e aggiungere il seguente codice nel 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; }
Ottenere il meglio da entrambe le soluzioni
C'è ancora qualcosa che si può fare. Il plugin Decorator
di Swift Mailer è molto utile visto che può gestire le sostituzioni in base al destinatario utilizzato. Questo significa che basta definire un insieme di sostituzioni per ogni destinatario e Swift Mailer di occuperà di rimpiazzare gli elementi con il giusto valore, in base al destinatario dell'email da inviare. Vediamo come poterlo integrare coi template:
public function execute($arguments = array(), $options = array()) { $message = Swift_Message::newInstance(); // recupera una lista di utenti 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; }
Con apps/frontend/templates/emails/user_specific_data.php
che contiene il seguente codice:
Hi {username}!
We just wanted to let you know your specific data:
{specific_data}
Ecco fatto! Ora avete a disposizione un completo sistema di template per generare il contenuto delle vostre email.
Generazione di URL
La scrittura di email solitamente richiede di generare degli URL basati sulla configurazione delle rotte. Per fortuna, la generazione degli URL è stata semplificata in symfony 1.3, a partire dal quale si può accedere direttamente al routing dell'applicazione corrente da dentro un task, usando il metodo sfCommandApplicationTask::getRouting()
:
public function execute($arguments = array(), $options = array()) { $routing = $this->getRouting(); }
Poiché le rotte dipendono dall'applicazione, occorre accertarsi che l'applicazione disponga di una configurazione, altrimenti non sarà possibile generare URL usando le rotte.
Si veda la sezione Opzioni Speciali per imparare come ottenere automaticamente una configurazione per l'applicazione in un task.
Ora che abbiamo un'istanza del routing, è abbastanza semplice generare un URL, usando il metodo generate()
:
public function execute($arguments = array(), $options = array()) { $url = $this->getRouting()->generate('default', array('module' => 'foo', 'action' => 'bar')); }
Il primo parametro è il nome della rotta e il secondo è un array di parametri per la rotta. A questo punto, abbiamo generato un URL relativo, che molto probabilmente non fa al caso nostro. Sfortunatamente, la generazione di URL assoluti in un task non funziona, perché non si dispone di un oggetto sfWebRequest
a cui appoggiarsi per recuperare l'host HTTP.
Un semplice modo per risolvere il problema consiste nell'impostare l'host HTTP nel file di configurazione factories.yml
:
---
all:
routing:
class: sfPatternRouting
param:
generate_shortest_url: true
extra_parameters_as_query_string: true
context:
host: example.org
Vedete l'impostazione context_host
? È quella che sarà usata dal routing per generare un URL assoluto:
public function execute($arguments = array(), $options = array()) { $url = $this->getRouting()->generate('my_route', array(), true); }
Accedere al Sistema I18N
Non tutti i factory sono così facilmente accessibili come il mailer e il routing. Se occorre accedere a uno di essi, non è molto difficile istanziarli. Ad esempio, se si desidera internazionalizzare i task, si avrà bisogno di accedere al sottosistema i18n di symfony. Lo si può fare facilmente usando 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; }
Vediamo che sta succedendo. Innanzitutto, stiamo usando una semplice tecnica di cache, per evitare di ricostruire il componente i18n a ogni chiamata. Poi, usando sfFactoryConfigHandler
, recuperiamo la configurazione del componente per poterlo istanziare. Infine impostiamo la configurazione della cultura. Il task ora ha accesso all'internazionalizzazione:
public function execute($arguments = array(), $options = array()) { $this->log($this->getI18N('fr')->__('some translated text!')); }
Ovviamente, passare ogni volta la cultura non è molto maneggevole, specialmente se non si ha bisogno di cambiare cultura molto spesso nel task. Vedremo come sistemare la cosa nella prossima sezione.
Rifattorizzare i Task
Poiché l'invio di email (compresa la generazione del loro contenuto) e la generazione degli URL sono due compiti molto comuni, potrebbe essere una buona idea quella di creare un task base che fornisca tali funzionalità per gli altri task. Lo si può fare facilmente. Basta creare una classe base nel progetto, ad esempio 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; } }
Già che ci siamo, automatizziamo la configurazione delle opzioni del task. Aggiungiamo i seguenti metodi alla classe sfBaseEmailTask
:
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); }
Usiamo il metodo configure()
per aggiungere opzioni comuni a tutti i task. Sfortunatamente, ogni classe che estende sfBaseEmailTask
dovrà richiamare parent::configure
nel proprio metodo configure()
, ma è una piccola noia per ottenere un grosso vantaggio.
Ora, rifattorizziamo il codice che accede all'I18N, visto nella precedente sezione:
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; }
Dobbiamo risolvere un problema: non è possibile accedere ai parametri e alle opzioni fuori da execute()
. Per risolverlo, ridefiniamo il metodo process()
per passare il gestore di opzioni alla classe. Il gestore di opzioni, come dice il nome stesso, gestisce i parametri e le opzioni per il task corrente. Ad esempio, si può accedere ai valori delle opzioni usando il metodo getOptionValue()
.
Eseguire un Task dentro un Task
Un modo alternativo per rifattorizzare i task è quello di includere un task dentro un altro task. Si può fare molto facilmente tramite i metodi sfCommandApplicationTask::createTask()
e sfCommandApplicationTask::runTask()
.
Il metodo createTask()
creerà per noi un'istanza di un task. Basta passargli il nome di un task, come se fossimo in linea di comando, e restituirà un'istanza del task scelto, pronta per essere eseguita:
$task = $this->createTask('cache:clear'); $task->run();
Ma sicomme siamo pigri, runTask
farà tutto al posto nostro:
$this->runTask('cache:clear');
Ovviamente, si possono passare parametri e opzioni (in questo ordine):
$this->runTask('plugin:install', array('sfGuardPlugin'), array('install_deps' => true));
Includere task è utile per comporre potenti task, a partire da task più semplici. Ad esempio, si possono combinare diversi task in un task project:clean
che si potrebbe eseguire dopo ogni 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); }
Manipolare il Filesystem
Symfony possiede una semplice astrazione per il filesystem (sfFilesystem
), che consente di eseguire semplici operazioni su file e cartelle. È accessibile da un task tramite $this->getFilesystem()
. Tale astrazione espone i seguenti metodi:
sfFilesystem::copy()
, per copiare un filesfFilesystem::mkdirs()
, crea ricorsivamente cartellesfFilesystem::touch()
, per creare un filesfFilesystem::remove()
, per cancellare un file o una cartellasfFilesystem::chmod()
, per cambiare i permessi di un file o di una cartellasfFilesystem::rename()
, per rinominare un file o una cartellasfFilesystem::symlink()
, per creare un collegamento a una cartellasfFilesystem::relativeSymlink()
, per creare un collegamento relativo a una cartellasfFilesystem::mirror()
, per fare il mirror di un intero albero di cartellesfFilesystem::execute()
, per eseguire un comando arbitrario
Espone anche un metodo molto utile, che analizzeremo nella prossima sezione: replaceTokens()
.
Usare degli Scheletri per generare i File
Un altro uso comune dei task è quello di generare file. I file possono essere generati facilmente, usando gli scheletri e il summenzionato metodo sfFilesystem::replaceTokens()
. Come il nome suggerisce, questo metodo sostituisce dei token dentro un insieme di file. Quindi, si passa un array di file, una lista di token ed esso sostituisce ogni occorrenza di ogni token col valore assegnato, in ogni file dell'array.
Per capire meglio la sua utilità, riscriveremo in parte un task esistente: generate:module
. A scopo di chiarezza e brevità, vedremo solo la parte execute
del task, ipotizzando che sia stato configurato propriamente con tutte le opzioni necessarie. Inoltre, salteremo la validazione.
Prima di iniziare a scrivere il task, dobbiamo creare uno scheletro per le cartelle e i file che stiamo per creare e salvarlo da qualche parte, come data/skeleton/
:
data/skeleton/
module/
actions/
actions.class.php
templates/
Lo scheletro actions.class.php
potrebbe essere qualcosa come questa:
class %moduleName%Actions extends %baseActionsClass% { }
Il primo passo del nostro task sarà copiare l'albero dei file nella posizione adeguata:
$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);
Ora sostituiamo i 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);
Ecco fatto, generiamo il nostro nuovo modulo, usando la sostituzione dei token per personalizzarlo.
Il task predefinito
generate:module
in realtà cerca indata/skeleton/
degli scheletri alternativi, da usare al posto di quelli standard, quindi fate attenzione!
Usare l'Opzione dry-run
Spesso si ha bisogno di poter vedere un'anteprima del risultato di un task, prima di eseguirlo effettivamente. Ecco un paio di trucchi per farlo.
Innanzitutto, si dovrebbe usare un nome standard, come dry-run
. Tutti lo riconoscono per quello che è. Fino a symfony 1.3, sfCommandApplication
aggiungeva un'opzione predefinita dry-run
, ma ora va aggiunta a mano (eventualmente in una classe base, come dimostrato sopra):
$this->addOption(new sfCommandOption('dry-run', null, sfCommandOption::PARAMETER_NONE, 'Executes a dry run');
Quindi si potrà richiamare il task in questo modo:
./symfony my:task --dry-run
L'opzione dry-run
indica che il task non dovrebbe eseguire cambiamenti.
Non dovrebbe eseguire cambiamenti, ricordatelo, sono parole chiave. In modalità dry-run
, il task deve lasciare l'ambiente esattamente com'era prima, incluso (ma non limitato a):
- Il database: non inserire, aggiornare o cancellare righe dalle tabelle. Si può usare una transazione per raggiungere lo scopo.
- Il filesystem: non creare, modificare o cancellare file dal filesystem.
- Invio di email: non inviare email né inviarle a indirizzi di debug.
Ecco un semplice esempio di uso dell'opzione dry-run
:
$connection->beginTransaction(); // modifica il database if ($options['dry-run']) { $connection->rollBack(); } else { $connection->commit(); }
Scrivere Test Unitari
Poiché i task possono raggiungere diversi scopi, testarli non è facile. Quindi, non c'è un unico modo per testare i task, ma ci sono alcuni principi da seguire, che posso aiutare a rendere i task più testabili.
Innanzitutto, pensare ai task come a un controllore. Ricordate la regola sui controllori? Controllori snelli, modelli grassi. Quindi, spostare tutta la logica di business dentro i modelli, in modo da poter testare i modelli invece del task, che è molto più facile.
Una volta che non si può spostare ulteriore logica nei modelli, dividere il metodo execute()
in pezzi di codice facilmente testabili, ognuno dei quali si appoggi su metodi propri facilmente accessibili (si legga: pubblici). Dividere il codice ha diversi benefici:
- rende l'
execute
del task più leggibile - rende il task più testabile
- rende il task più estensibile
Siate creativi, non esitate a costruire un piccolo ambiente specifico per le vostre esigenze di test. E se non trovate un modo per testare quel task stupendo che avete appena scritto, ci sono due possibilità: o l'avete scritto male, o dovreste chiedere l'opinione di qualcun altro. Inoltre, si può sempre analizzare il codice altrui, per vedere come gli altri testano le cose (i task di symfony, ad esempio, sono molto ben testati, anche i generatori).
Metodi di Aiuto: Log
Il sistema dei task di symfony prova fortemente a rendere la vita dello sviluppatore più facile, fornendo dei metodi di aiuto per operazioni comuni, come scrivere nei log e interagire con l'utente.
Si possono facilmente mandare in output dei messaggi, usando la famiglia dei metodi log
:
log
, accetta un array di messaggilogSection
, un po' più elaborato, formatta il messaggio con un prefisso (primo parametro) e un tipo di messaggio (quarto parametro). Quando si inserisce un messaggio troppo lungo, come il percorso di un file,logSection
solitamente lo accorcia, il che potrebbe risultare fastidioso. Usare il terzo parametro per specificare una dimensione massima per il proprio messaggio.logBlock
è lo stile di messaggi usato per le eccezioni. Anche qui, si può passare uno stile di formattazione
I formati di log disponibili sono ERROR
, INFO
, COMMENT
e QUESTION
. Usateli pure per vedere in che modo si comportano.
Esempio di utilizzo:
$this->logSection('file+', $aVeryLongFileName, $this->strlen($aVeryLongFileName)); $this->logBlock('Congratulations! You ran the task successfuly!', 'INFO');
Metodi di Aiuto: Interazione con l'Utente
Altri tre metodi d'aiuto facilitano l'interazione con l'utente:
-
ask()
scrive semplicemente una domanda e restituisce la risposta dell'utente -
askConfirmation()
chiede una conferma all'utente, consentendoy
(sì) en
(no) come risposta -
askAndValidate()
, molto utile, scrive una domanda e valida la risposta dell'utente tramite unsfValidator
, passato come secondo parametro. Il terzo parametro è un array di opzioni in cui si può passare un valore predefinito (value
), un numero massimo di tentativi (attempts
) e uno stile di formattazione (style
).
Ad esempio, si può chiedere all'utente il suo indirizzo email e validarlo al volo:
$email = $this->askAndValidate('What is your email address?', new sfValidatorEmail());
Bonus: Usare i Task con Crontab
Quasi tutti i sistemi UNIX e GNU/Linux consentono la pianificazione di task, tramite un meccanismo noto come cron. Il cron controlla un file di configurazione (chiamato crontab), che contiene comandi da eseguire a determinati orari. I task di symfony possono essere facilmente integrati nel crontab e il task project:send-emails
è un perfetto candidato come esempio:
MAILTO="[email protected]"
0 3 * * * /usr/bin/php /var/www/yourproject/symfony project:send-emails
Questa configurazione dice a cron di eseguire project:send-emails
ogni giorno alle 3 del mattino e di inviare ogni possibile output (log, errori, ecc.) all'indirizzo [email protected].
Per maggiori informazioni sulla configurazione di crontab, si può scrivere
man 5 crontab
in un terminale.
Si può, e in realtà si dovrebbe, passare parametri e opzioni:
MAILTO="[email protected]"
0 3 * * * /usr/bin/php /var/www/yourproject/symfony project:send-emails --env=prod --application=frontend
Si dovrebbe sostituire
/usr/bin/php
con il percorso del binario PHP CLI. Se non di dispone di questa informazione, si proviwhich php
su Linux owhereis php
sulla maggior parte degli altri UNIX.
Bonus: Usare STDIN
Essendo i task eseguiti in un ambiente a linea di comando, si può accedere allo standard input stream (STDIN). La linea di comando UNIX consente alle applicazioni di interagire tra di loro in molti modi, uno dei quali è il pipe, identificato dal carattere |. Il pipe consente di passare l'output di un'applicazione (noto come STDOUT) allo standard input di un'altra applicazione (noto come STDIN). Entrambi sono accessibili dai task tramite le costanti speciali di PHP STDIN
e STDOUT
. C'è anche un terzo flusso standard, STDERR, accessibile tramite STDERR
, che si occupa dei messaggi di errore di un'applicazione.
Dunque, cosa possiamo fare esattamente con lo standard input? Be', immaginate di avere un'applicazione che gira sul server, che vorrebbe comunicare con un'applicazione symfony. Si potrebbe ovviamente farla comunicare tramite HTTP, ma un modo più efficiente sarebbe quello di concatenare tramite pipe il suo output con un task symfony. Supponiamo che l'applicazione possa inviare dati strutturati (ad esempio un array PHP serializzato) che descrive degli oggetti di dominio che si vuole includere nel database. Si potrebbe scrivere il seguente task:
while ($content = trim(fgets(STDIN))) { if ($data = unserialize($content) !== false) { $object = new Object(); $object->fromArray($data); $object->save(); } }
E lo si potrebbe usare in questo modo:
/usr/bin/data_provider | ./symfony data:import
dove data_provider
è l'applicazione che fornisce nuovi oggetti di dominio e data:import
il task appena scritto.
Considerazioni Finali
Quello che si può ottenere coi task è limitato solo dalla propria immaginazione. Il sistema dei task di symfony è abbastanza potente e flessibile da poter fare quasi ogni cosa vi possa venire in mente. Aggiungete la potenza del terminale UNIX e amerete veramente i task.
インデックス
Document Index
-
Sfruttare la potenza della linea di comando
- Task al primo sguardo
- Scrivere un proprio task
- Il sistema delle opzioni
- Accedere al database
- Inviare email
- Generazione di URL
- Accedere al Sistema I18N
- Rifattorizzare i Task
- Eseguire un Task dentro un Task
- Manipolare il Filesystem
- Usare degli Scheletri per generare i File
- Usare l'Opzione dry-run
- Scrivere Test Unitari
- Metodi di Aiuto: Log
- Metodi di Aiuto: Interazione con l'Utente
- Bonus: Usare i Task con Crontab
- Bonus: Usare STDIN
- Considerazioni Finali
関連ページリスト
Related Pages
Introduzione
Utilizzo avanzato delle rotte
Migliorare la propria produttività
Email
Widget e validatori personalizzati
L'utilizzo avanzato dei form
Estendere la Web Debug Toolbar
Uso avanzato di Doctrine
Sfruttare l'ereditarietà delle tabelle di Doctrine
Symfony all'interno
Windows e symfony
Sviluppare su Facebook
Sfruttare la potenza della linea di comando
Lavorare con la cache della configurazione di symfony
Lavorare con la comunità di symfony
Appendice A - codice JavaScript per sfWidgetFormGMapAddress
A proposito degli autori
Appendice B - Esempio di installazione personalizzata
Appendice C - Licenza
A proposito dei traduttori

日本語ドキュメント
Japanese Documents
2011/01/18 Chapter 17 - Extending Symfony
2011/01/18 The generator.yml Configuration File
2011/01/18 Les tâches
2011/01/18 Emails
2010/11/26 blogチュートリアル(8) ビューの作成
リリース情報
Release Information
- 2.0 : 2.0.15(2011/05/30)
Symfony2日本語ドキュメント - 1.4 : 1.4.18(2012/05/30)
Changelog

