The Components Book Version: 2.7
|
|
|
- Mariana Raimondi
- 10 anni fa
- Visualizzazioni
Transcript
1 The Components Book Version:. generated on August, 0
2 The Components Book (.) This work is licensed under the Attribution-Share Alike.0 Unported license ( licenses/by-sa/.0/). You are free to share (to copy, distribute and transmit the work), and to remix (to adapt the work) under the following conditions: Attribution: You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work). Share Alike: If you alter, transform, or build upon this work, you may distribute the resulting work only under the same, similar or a compatible license. For any reuse or distribution, you must make clear to others the license terms of this work. The information in this book is distributed on an as is basis, without warranty. Although every precaution has been taken in the preparation of this work, neither the author(s) nor SensioLabs shall have any liability to any person or entity with respect to any loss or damage caused or alleged to be caused directly or indirectly by the information contained in this work. If you find typos or errors, feel free to report them by creating a ticket on the Symfony ticketing system ( Based on tickets and users feedback, this book is continuously updated.
3 Contents at a Glance Installare e usare i componenti di Symfony... Il componente ClassLoader... Class Loader PSR Caricatore di classi PSR-... MapClassLoader... Cache di Class Loader... Debug di un ClassLoader... Generatore di classi di mappe... Il componente Config... Caricare risorse... Cache basata sulle risorse... Definire e processare valori di configurazione... Il componente Console... Uso di Console... Cambiare comando predefinito...0 Come costruire un'applicazione in un singolo comando... Capire come sono gestiti i parametri della console... Usare gli eventi... Uso del Logger... Aiutante Dialog... Aiutante Formatter... <no title>... Barra di progressione...0 Aiutante Progress... Aiutante Question... Tabella... Aiutante Table... <no title>... Il componente CssSelector...0 Il componente Debug... Debug di ClassLoader... Il componente DependencyInjection... Tipi di iniezione Introduzione ai parametri... 0 Lavorare con parametri e definizioni del contenitore... 0 Compilazione del contenitore... generated on August, 0 Contents at a Glance iii
4 Usare i tag nei servizi... 0 Usare un factory per creare servizi... Configurare servizi con un configuratore di servizi... Come gestire le dipendenze comuni con servizi genitori... Configurazione avanzata del contenitore... Servizi pigri... Flusso di costruzione del contenitore... Il componente DomCrawler... Il componente EventDispatcher... 0 Il distributore consapevole del contenitore... Oggetto evento generico... L'Event Dispatcher Immutable... Distributore di eventi tracciabile... Il componente ExpressionLanguage... Sintassi di Expression... Estendere ExpressionLanguage... Cache di espressioni analizzate... 0 Il componente Filesystem... LockHandler... Il componente Finder... Il componente Form... Creare un indovino di tipi... 0 Eventi dei form... Il componente HttpFoundation... Gestione della sessione... Configurare sessioni e gestori di salvataggio... 0 Test con le sessioni... Integrazione con sessioni legacy... Proxy fidati... 0 Il componente HttpKernel... Il componente Intl... Il componente OptionsResolver... Il componente Process... Il componente PropertyAccess... Il componente Routing Corrispondere una rotta in base all'host... 0 Il componente Security... 0 Il Firewall e il contesto di sicurezza... Autenticazione... Autorizzazione... 0 Sicurezza nel confronto di stringhe e nella generazione di numeri casuali... Il componente Serializer... Il componente Stopwatch... Il componente Templating... Aiutante slots... Aiutante per gli asset... Il componente Translation... iv Contents at a Glance Contents at a Glance
5 Uso di Translator... Aggiungere supporto per un formato personalizzato... Il componente VarDumper... Utilizzo avanzato del componente VarDumper... Il componente YAML... Il formato YAML... generated on August, 0 Contents at a Glance v
6 Chapter Installare e usare i componenti di Symfony Se si inizia un nuovo progetto (o se si ha già un progetto) che userà uno o più componenti, il modo più semplice per integrare tutto è con Composer. Composer è abbastanza intelligente da scaricare i componenti necessari e occuparsi del caricamento automatico, in modo che si può iniziare a usare immediatamente le librerie. Questo articolo approfondirà l'uso di Il componente Finder, tuttavia è applicabile all'uso di qualsiasi componente. Uso del componente Finder. Se si sta creando un nuovo progetto, creare una cartella vuota.. Creare un file chiamato composer.json e incollarvi dentro il codice seguente: Listing - $ composer require symfony/finder Il nome symfony/finder è scritto in cima alla documentazione del componente desiderato. Installare composer, se non fosse già presente sul sistem. A seconda di come lo si installa, si potrebbe avere un file composer.phar nella cartella. In questo caso, nessun problema! Basta eseguire php composer.phar require symfony/finder. Se si sa di aver bisogno di una versione specifica della libreria, aggiungerla al comando: Listing - $ composer require symfony/finder:... Scrivere il proprio codice!. generated on August, 0 Chapter : Installare e usare i componenti di Symfony
7 Una volta che Composer ha scaricato i componenti, basterà includere il file vendor/autoload.php generato da Composer stesso. Tale file si occupa di autocaricare tutte le librerie, in modo che si possano usare immediatamente: Listing - 0 // File esempio: src/script.php // cambiare il percorso in quello della cartella "vendor/" // relativamente a questo file require_once DIR.'/../vendor/autoload.php'; use Symfony\Component\Finder\Finder; $finder = new Finder(); $finder->in('../data/'); Usare tutti i componenti Se si vogliono usare tutti i componenti di Symfony, invece di aggiungerli uno per uno, si può includere il pacchetto symfony/symfony: Listing - $ composer require symfony/symfony Questo includerà anche librerie di bundle e di bridge, che potrebbero non essere effettivamente necessarie. E ora? Ora che i componenti sono installati e autocaricati, leggere la documentazione specifica dei componenti per saperne di più sul loro uso. Buon divertimento! generated on August, 0 Chapter : Installare e usare i componenti di Symfony
8 Chapter Il componente ClassLoader Il componente ClassLoader carica le classi di un progetto automaticamente, purché seguano alcune convenzioni standard di PHP. Uso Ogni volta che si usa una classe non ancora richiesta o inclusa, PHP utilizza il meccanismo di autocaricamento per delegare il caricamento di un file che definisca la classe. Symfony fornisce due autoloader, capaci di caricare classi: Class Loader PSR-0: carica classi che seguono lo standard dei nomi PSR-0; Caricatore di classi PSR-: carica classi che seguono lo standard dei nomi PSR-; MapClassLoader: carica classi che usano una mappa statica dal nome della classe al percorso del file. Inoltre, il componente ClassLoader di Symfony dispone di un insieme di classi wrapper, che si possono usare per aggiungere funzionalità agli autoloader esistenti: Cache di Class Loader Debug di un ClassLoader Installazione Si può installare il componente in due modi: Installarlo via Composer (symfony/class-loader su Packagist ); Usare il repository ufficiale su Git ( ) generated on August, 0 Chapter : Il componente ClassLoader
9 Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. generated on August, 0 Chapter : Il componente ClassLoader
10 Chapter Class Loader PSR-0 Se si usano classi e librerie di terze parti che seguono lo standard PSR-0, si può usare la classe ClassLoader per caricare tutte le classi del progetto. Si possono usare sia ApcClassLoader sia XcacheClassLoader per mettere in cache un'istanza di ClassLoader o di DebugClassLoader per il debug. Uso La registrazione dell'autoloader ClassLoader è semplice: Listing - 0 require_once '/path/to/src/symfony/component/classloader/classloader.php'; use Symfony\Component\ClassLoader\ClassLoader; $loader = new ClassLoader(); // per abilitare la ricerca in include_path (per esempio per i pacchetti PEAR) $loader->useincludepath(true); registrare qui spazi di nomi e prefissi, vedere più avanti $loader->register(); In un'applicazione Symfony, l'autoloader è registrato automaticamente (vedere app/ autoload.php) generated on August, 0 Chapter : Class Loader PSR-0 0
11 Usare i metodi addprefix() o addprefixes() per registrare le classi: Listing - 0 // registra un singolo spazio di nomi $loader->addprefix('symfony', DIR.'/vendor/symfony/symfony/src'); // registra più spazi di nomi $loader->addprefixes(array( 'Symfony' => DIR.'/../vendor/symfony/symfony/src', 'Monolog' => DIR.'/../vendor/monolog/monolog/src', )); // registra un prefisso di una classe che segue le convenzioni di PEAR $loader->addprefix('twig_', DIR.'/vendor/twig/twig/lib'); $loader->addprefixes(array( 'Swift_' => DIR.'/vendor/swiftmailer/swiftmailer/lib/classes', 'Twig_' => DIR.'/vendor/twig/twig/lib', )); Si possono cercare le classi di un sotto-spazio dei nomi o di una sotto-gerarchia di classi PEAR in una lista di posizioni, per facilitare la gestione dei venditori di un sottoinsieme di classi per grandi progetti: Listing - $loader->addprefixes(array( 'Doctrine\\Common' => DIR.'/vendor/doctrine/common/lib', 'Doctrine\\DBAL\\Migrations' => DIR.'/vendor/doctrine/migrations/lib', 'Doctrine\\DBAL' => DIR.'/vendor/doctrine/dbal/lib', 'Doctrine' => DIR.'/vendor/doctrine/orm/lib', )); In questo esempio, se si prova a usare una classe nello spazio dei nomi Doctrine\Common o uno dei suoi figli, l'autoloader cercherà prima la classe sotto la cartella doctrine-common. Se non trovata, ripiegherà alla cartella predefinita Doctrine (l'ultima configurata), prima di arrendersi. L'ordine delle registrazioni dei prefissi, in questo caso, è significativo generated on August, 0 Chapter : Class Loader PSR-0
12 Chapter Caricatore di classi PSR- New in version.: PsrClassLoader è stato introdotto in Symfony.. Si possono caricare le librerie che seguono lo standard PSR- con PsrClassLoader. Se si gestiscono le dipendenza tramite Composer, si ha giò un autoloader comptabilie con PSR-. Usare questo caricatore in ambienti in cui Composer non sia disponibile. Tutti i componenti di Symfony seguono PSR-. Uso L'esempio seguente dimostra come si possa usare l'autoloader PsrClassLoader per il componente Yaml di Symfony. Si immagini di aver scaricato sia ClassLoader sia il componente Yaml come ZIP e di averli scompattati in una cartella libs. La struttura della cartella assomiglierà a questa: Listing - libs/ ClassLoader/ PsrClassLoader.php... Yaml/ Yaml.php... config.yml demo.php generated on August, 0 Chapter : Caricatore di classi PSR-
13 In demo.php, si analizzerà il file config.yml. Per poterlo fare, occorre prima configurare PsrClassLoader: Listing - 0 use Symfony\Component\ClassLoader\PsrClassLoader; use Symfony\Component\Yaml\Yaml; require DIR.'/lib/ClassLoader/PsrClassLoader.php'; $loader = new PsrClassLoader(); $loader->addprefix('symfony\\component\\yaml\\', DIR.'/lib/Yaml'); $loader->register(); $data = Yaml::parse( DIR.'/config.yml'); Prima di tutto, il caricatore viene caricato manualmente, usando un'istruzione require, perché non c'è ancora un meccanismo di caricamento automatico. Con la chiamata a addprefix(), si dice al caricatore di classi di cercare classi con prefisso Symfony\Component\Yaml\ nello spazio dei nomi. Dopo aver registrato l'autoloader, il componente Yaml è pronto all'uso.. generated on August, 0 Chapter : Caricatore di classi PSR-
14 Chapter MapClassLoader La classe MapClassLoader consente di auto-caricare file tramite una mappa statica, dalle classi ai file. È utile se si usano librerie di terze parti, che non seguono lo standard PSR-0 e quindi non possono usare class loader PSR-0. Si può usare MapClassLoader insieme a class loader PSR-0, configurando e richiamando su entrambi il metodo register(). Il comportamento predefinito è di appendere MapClassLoader alla pila di auto-caricamento. Se lo si vuole usare come primo autoloader, passare true al metodo register(). In questo caso, il class loader sarà messo in cima alla pila di auto-caricamento. Uso È facile, basta passare la mappa al costruttore, quando si crea un'istanza della classe MapClassLoader: Listing - 0 require_once '/path/to/src/symfony/component/classloader/mapclassloader'; $mapping = array( 'Pippo' => '/percorso/di/pippo', 'Pluto' => '/percorso/di/pluto', ); $loader = new MapClassLoader($mapping); $loader->register();. generated on August, 0 Chapter : MapClassLoader
15 Chapter Cache di Class Loader Introduzione Trovare un file per una classe specifica può essere pesante. Per fortuna, il componente Class Loader dispone di due classi per la cache della mappatura da classe a file. Sia ApcClassLoader che XcacheClassLoader sono wrapper intorno all'oggetto che implementa un metodo findfile(), per trovare il file di una classe. Sia ApcClassLoader che XcacheClassLoader possono essere usati per la cache dell'autoloader di Composer. ApcClassLoader ApcClassLoader è un wrapper di un class loader esistente e mette in cache le chiamate al suo metodo findfile(), usando APC : Listing - require_once '/path/to/src/symfony/component/classloader/apcclassloader.php'; // istanza di una classe che implementa un metodo findfile(), come ClassLoader $loader =...; // mio_prefisso è il prefisso da usare in APC $cachedloader = new ApcClassLoader('mio_prefisso', $loader); // registra il class loader in cache generated on August, 0 Chapter : Cache di Class Loader
16 0 $cachedloader->register(); // disattiva il loader originale, non in cache, se era stato precedentemente registrato $loader->unregister(); XcacheClassLoader XcacheClassLoader usa XCache per mettere in cache un class loader. La registrazione è semplice: Listing - 0 require_once '/path/to/src/symfony/component/classloader/xcacheclassloader.php'; // istanza di una classe che implementa un metodo findfile(), come ClassLoader $loader =...; // mio_prefisso è il prefisso da usare in XCache $cachedloader = new XcacheClassLoader('mio_prefisso', $loader); // registra il class loader in cache $cachedloader->register(); // disattiva il loader originale, non in cache, se era stato precedentemente registrato $loader->unregister();. generated on August, 0 Chapter : Cache di Class Loader
17 Chapter Debug di un ClassLoader A partire da Symfony., DebugClassLoader del componente ClassLoader è deprecato. Usare DebugClassLoader fornito dal componente Debug. generated on August, 0 Chapter : Debug di un ClassLoader
18 Chapter Generatore di classi di mappe Il caricamento di una classe è solitamente facile, con gli standard PSR-0 e PSR-. Grazie al componente ClassLoader di Symfony o al meccanismo fornito da Composer, non occorre mappare manualmente i nomi di classi ai file PHP. Oggigiorno, le librerie PHP solitamente dispongono di un supporto per il caricamento tramite Composer. A volte però capita di usare librerie di terze parti che non dispongono di un supporto per il caricamento, che costringono quindi a caricare ogni classe a mano. Per esempio, si immagini una libreria con la seguente struttura di cartelle: Listing - libreria/ pippo/ quiquoqua/ Paperone.php Pippo.php pluto/ paperino/ Pippo.php Paperrino.php Questi file contengono le seguenti classi: File libreria/pluto/paperino/paperone.php libreria/pluto/pippo.php libreria/pippo/pluto/pippo.php libreria/pippo/pluto.php Nome classe Acme\Pluto\Paperino Acme\Pluto Acme\Pippo\Pluto Acme\Pippo Per facilitare le cose, il componente ClassLoader dispone di una classe ClassMapGenerator, che rende possibile creare una mappa di nomi di classi e file generated on August, 0 Chapter : Generatore di classi di mappe
19 Generare una mappa di classi Per generare una mappa di classi, basta passare la cartella radice dei file delle classi al metodo createmap() : Listing - use Symfony\Component\ClassLoader\ClassMapGenerator; print_r(classmapgenerator::createmap( DIR.'/library')); Dati file e classi della tabella precedente, si dovrebbe ottenere un output come questo: Listing - Array ( [Acme\Pippo] => /var/www/library/pippo/pluto.php [Acme\Pippo\Pluto] => /var/www/library/pippo/pluto/pippo.php [Acme\Pluto\Paperino] => /var/www/library/pluto/paperino/paperone.php [Acme\Pluto] => /var/www/library/pluto/pippo.php ) Esportare la mappa di classi La scrittura della mappa di classi sulla console non è sufficiente per il caricamento automatico. Per fortuna, ClassMapGenerator dispone di un metodo dump(), per salvare la mappa di classi generata su filesystem: Listing - use Symfony\Component\ClassLoader\ClassMapGenerator; ClassMapGenerator::dump( DIR.'/library', DIR.'/class_map.php'); Questa chiamata a dump() genera la mappa di classi e la scrive nel file class_map.php nella stessa cartella, con il seguente contenuto: Listing - <?php return array ( 'Acme\\Pippo' => '/var/www/library/pippo/pluto.php', 'Acme\\Pippo\\Pluto' => '/var/www/library/pippo/pluto/pippo.php', 'Acme\\Pluto\\Baz' => '/var/www/library/pluto/paperino/paperone.php', 'Acme\\Pluto' => '/var/www/library/pluto/pippo.php', ); Invece di caricare ogni file a mano, basta generare la mappa di classi generata, per esempio usando MapClassLoader : Listing - use Symfony\Component\ClassLoader\MapClassLoader; $mapping = include DIR.'/class_map.php'; $loader = new MapClassLoader($mapping); $loader->register(); // ora si possono usare le classi: generated on August, 0 Chapter : Generatore di classi di mappe
20 0 use Acme\Pippo; $pippo = new Pippo(); L'esempio ipotizza che si abbia già un autoloader funzionante (p.e. tramite Composer o uno dei caricatori di classi del componente ClassLoader. Oltre a esportare la mappa di classi per una cartella, si può anche passare un array di cartelle per cui generare la mappa di classi (il risultato è lo stesso dell'esempio precedente): Listing - use Symfony\Component\ClassLoader\ClassMapGenerator; ClassMapGenerator::dump( array( DIR.'/library/pluto', DIR.'/library/pippo'), DIR.'/class_map.php' );. generated on August, 0 Chapter : Generatore di classi di mappe 0
21 Chapter Il componente Config Il componente Config fornisce diverse classi che aiutano a trovare, caricare, combinare, riempire e validare valori di configurazione di ogni tipo, indipendentemente dal tipo di sorgente (file YAML, XML o INI, oppure ad esempio una base dati). IniFileLoader analizza i contenuti dei file usando la funzione parse_ini_file, quindi si possono impostare solamente parametri stringa. Per impostare tipi diversi di parametri (p.e. booleani, interi, ecc), si raccomanda l'uso di altri caricatori. Installazione Si può installare il componente in due modi: Installarlo tramite Composer (symfony/config su Packagist ); Usare il repository ufficiale su Git ( ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Sezioni Caricare risorse Cache basata sulle risorse Definire e processare valori di configurazione generated on August, 0 Chapter : Il componente Config
22 Chapter 0 Caricare risorse Trovare le risorse Il caricamento della configurazione solitamente inizia con la ricerca delle risorse, nella maggior parte dei casi dei file. Lo si può fare con FileLocator : Listing 0- use Symfony\Component\Config\FileLocator; $configdirectories = array( DIR.'/app/config'); $locator = new FileLocator($configDirectories); $yamluserfiles = $locator->locate('utenti.yml', null, false); Il cercatore di risorse riceve un insieme di posizioni in cui cercare file. Il primo parametro di locate() è il nome del file da cercare. Il secondo parametro può essere il percorso e, se fornito, il cercatore cercherà prima in tale cartella. Il terzo parametro indica se il cercatore debba restituire il primo file trovato oppure un array con tutte le corrispondenze. Caricatori di risorse Per ciascun tipo di risorsa (Yaml, XML, annotazioni, ecc.) va definito un caricatore. Ogni caricatore deve implementare LoaderInterface o estendere la classe astratta FileLoader, che consente di importare ricorsivamente altre risorse: Listing 0- use Symfony\Component\Config\Loader\FileLoader; use Symfony\Component\Yaml\Yaml; generated on August, 0 Chapter 0: Caricare risorse
23 0 0 class YamlUserLoader extends FileLoader public function load($resource, $type = null) $configvalues = Yaml::parse(file_get_contents($resource)); gestione dei valori di configurazione // possibile importazione di altri risorse: // $this->import('altri_utenti.yml'); public function supports($resource, $type = null) return is_string($resource) && 'yml' === pathinfo( $resource, PATHINFO_EXTENSION ); Trovare il giusto caricatore La classe LoaderResolver riceve un insieme di caricatori come primo parametro del suo costruttore. Quando una risorsa (per esempio un file XML) va caricata, cerca in questo insieme di caricatori e restituisce il caricatore che supporta questo particolare tipo di risorsa. La classe DelegatingLoader fa uso di LoaderResolver. Quando gli viene richiesto di caricare una risorsa, delega la questione a LoaderResolver. Se quest'ultimo trova un caricatore adatto, a tale caricatore sarà chiesto di caricare la risorsa: Listing 0-0 use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\Config\Loader\DelegatingLoader; $loaderresolver = new LoaderResolver(array(new YamlUserLoader($locator))); $delegatingloader = new DelegatingLoader($loaderResolver); $delegatingloader->load( DIR.'/utenti.yml'); /* Sarà usato YamlUserLoader per caricare questa risorsa, poiché supporta file con estensione "yml" */ generated on August, 0 Chapter 0: Caricare risorse
24 Chapter Cache basata sulle risorse Quando tutte le risorse di configurazione sono state caricate, si potrebbero voler processare i valori di configurazione e combinarli un unico file. Questo file agisce da cache. I suoi contenuti non devono essere rigenerati ogni volta che gira l'applicazione, ma solo quando le risorse di configurazione vengono modificate. Per esempio, il componente Routing di Symfony consente di caricare tutte le rotte e poi di esportare un matcher di UL o un generatore di URL, basati su tali rotte. In questo caso, quando una delle risorse viene modificata (e si sta lavorando in un ambiente di sviluppo), il file generato va invalidato e rigenerato. Si può ottenere questo risultato usando la classe ConfigCache. L'esempio successivo mostra come raccogliere le risorse e generare un codice, basato sulle risorse caricate, e scrivere tale codice in cache. La cache riceve anche l'insieme di risorse usate per generare il codice. Cercando il timestamp "last modified" di tali risorse, la cache può dirci se è ancora fresca o se i suoi contenuti vanno rigenerati: Listing - 0 use Symfony\Component\Config\ConfigCache; use Symfony\Component\Config\Resource\FileResource; $cachepath = DIR.'/cache/appUserMatcher.php'; // il secondo parametro indica se si è in debug o meno $usermatchercache = new ConfigCache($cachePath, true); if (!$usermatchercache->isfresh()) // inserire un array di percorsi per il file 'utenti.yml' $yamluserfiles =...; $resources = array(); foreach ($yamluserfiles as $yamluserfile) // vedere la voce precedente "Caricare risorse" per // capire da dove viene $delegatingloader $delegatingloader->load($yamluserfile); $resources[] = new FileResource($yamlUserFile);. generated on August, 0 Chapter : Cache basata sulle risorse
25 0 // il codice per UserMatcher è generato altrove $code =...; $usermatchercache->write($code, $resources); // si potrebbe voler richiedere il codice in cache: require $cachepath; In debug, sarà creato un file.meta nella stessa cartella del file di cache stesso. Tale file.meta contiene le risorse serializzate, i cui timestamp sono usati per determinare se la cache è ancora fresca. Se non si è in debug, la cache è considerata fresca fintanto che esiste, per cui non viene generato alcun file.meta. generated on August, 0 Chapter : Cache basata sulle risorse
26 Chapter Definire e processare valori di configurazione Validare i valori di configurazione Dopo aver caricato i valori di configurazione da ogni tipo di risorsa, i valori e le loro strutture possono essere validati, usando la parte "Definition" del componente Config. Solitamente ci si aspetta che i valori di configurazione mostrino un qualche tipo di gerarchia. Inoltre, i valori dovrebbero essere di un certo tipo, ristretti in numero o all'interno di un determinato insieme di valori. Per esempio, la configurazione seguente (in YAML) mostra una chiara gerarchia e alcune regole di validazione che vi andrebbero applicate (come: "il valore per auto_connect deve essere booleano"): Listing - 0 auto_connect: true default_connection: mysql connections: mysql: host: localhost driver: mysql username: utente password: pass sqlite: host: localhost driver: sqlite memory: true username: utente password: pass Quando si caricano diversi file di configurazione, dovrebbe essere possibile fondere e sovrascrivere alcuni valori. Gli altri valori non vanno fusi e devono rimanere come prima. Inoltre, alcune chiavi sono disponibili solo quando un altra chiave ha uno specifico valore (nell'esempio precedente: la chiave memory ha senso solo quando driver è sqlite). generated on August, 0 Chapter : Definire e processare valori di configurazione
27 Definire una gerarchia di valori di configurazione con TreeBuilder Tutte le regole relative ai valori di configurazione possono essere definite tramite TreeBuilder. Un'istanza di TreeBuilder va restituita da una classe personalizzata Configuration, che implementa ConfigurationInterface : Listing - 0 namespace Acme\DatabaseConfiguration; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Builder\TreeBuilder; class DatabaseConfiguration implements ConfigurationInterface public function getconfigtreebuilder() $treebuilder = new TreeBuilder(); $rootnode = $treebuilder->root('database'); aggiungere definizioni di nodi alla radice dell'albero return $treebuilder; Aggiungere definizioni di nodi all'albero Nodi variabili Un albero contiene definizioni di nodi, che possono essere stratificati in modo semantico. Questo vuol dire che, usando l'indentazione e la notazione fluida, è possibile riflettere la reale struttura dei valori di configurazione: Listing - 0 $rootnode ->children() ->booleannode('auto_connessione') ->defaulttrue() ->end() ->scalarnode('connessione_predefinita') ->defaultvalue('predefinito') ->end() ->end() ; Lo stesso nodo radice è un nodo array e ha dei figli, come il nodo booleano auto_connect e il nodo scalare default_connection. In generale: dopo aver definito un nodo, una chiamata end() porta un gradino in alto nella gerarchia generated on August, 0 Chapter : Definire e processare valori di configurazione
28 Tipo di nodo Si può validare il tipo di un valore fornito, usando l'appropriata definizione di nodo. I tipi di nodo disponibili sono: scalare (tipo generico che include booleani, stringhe, interi, virgola mobile e null) booleano intero virgola mobile enum (simile a scalare, ma consente solo un insieme determinato di valori) array variabile (nessuna validazione) e sono creati con node($nome, $tipo) o con i relativi metodi scorciatoia xxxxnode($nome). Nodi di vincoli numerici I nodi numerici (virgola mobile e intero) foniscono due vincoli extra, min() e max(), che consentono di validare il valore: Listing - 0 $rootnode ->children() ->integernode('valore_positivo') ->min(0) ->end() ->floatnode('valore_grosso') ->max(e) ->end() ->integernode('valore_tra_estremi') ->min(-0)->max(0) ->end() ->end() ; Nodi enum I nodi enum forniscono un vincolo che fa corrispondere il dato inserito a una serie di valori: Listing - $rootnode ->children() ->enumnode('genere') ->values(array('maschio', 'femmina')) ->end() ->end() ; Questo restringe l'opzione genere ai valori maschio o femmina. Nodi array Si può aggiungere un livello ulteriore alla gerarchia, aggiungendo un nodo array. Il nodo array stesso potrebbe avere un insieme predefinito di nodi variabili: Listing generated on August, 0 Chapter : Definire e processare valori di configurazione
29 0 $rootnode ->children() ->arraynode('connection') ->children() ->scalarnode('driver')->end() ->scalarnode('host')->end() ->scalarnode('utente')->end() ->scalarnode('password')->end() ->end() ->end() ->end() ; Oppure si può definire un prototipo per ogni nodo dentro un nodo array: Listing - 0 $rootnode ->children() ->arraynode('connections') ->prototype('array') ->children() ->scalarnode('driver')->end() ->scalarnode('host')->end() ->scalarnode('utente')->end() ->scalarnode('password')->end() ->end() ->end() ->end() ->end() ; Si può usare un prototipo per aggiungere una definizione, che potrebbe essere ripetuta molte volte dentro il nodo corrente. In base alla definizione del prototipo nell'esempio precedente, è possibile avere molte array di connessione (contenenti driver, host, ecc.). Opzioni dei nodi array Prima di definire i figli di un nodo array, si possono fornire opzioni, come: useattributeaskey() Fornisce il nome di un nodo figlio, i cui valori sono usati come chiavi nell'array risultante requiresatleastoneelement() Dovrebbe esserci almeno un elemento nell'array (funziona solo se viene richiamato anche isrequired()). adddefaultsifnotset() Se dei nodi figli hanno valori predefiniti, usarli se non sono stati forniti dati espliciti. Un esempio: Listing - $rootnode ->children() ->arraynode('parameters') ->isrequired() ->requiresatleastoneelement() ->useattributeaskey('nome') ->prototype('array') generated on August, 0 Chapter : Definire e processare valori di configurazione
30 0 ; ->children() ->scalarnode('valore')->isrequired()->end() ->end() ->end() ->end() ->end() In YAML, la configurazione potrebbe essere come questa: Listing - database: parameters: param: valore: paramval In XML, ciascun nodo parameters avrebbe un attributo name (insieme a value), che sarebbe rimosso e usato come chiave per tale elemento nell'array finale. L'opzione useattributeaskey è utile per normalizzare il modo in cui gli array sono specificati tra formati diversi, come XML e YAML. Valori predefiniti e obbligatori Per tutti i tipi di nodo, è possibile definire valori predefiniti e valori di rimpiazzo nel caso in cui un nodo abbia un determinato valore: defaultvalue() Imposta un valore predefinito isrequired() Deve essere definito (ma può essere vuoto) cannotbeempty() Non può contenere un valore vuoto default*() (null, true, false), scorciatoia per defaultvalue() treat*like() (null, true, false), fornisce un valore di rimpiazzo in caso in cui il valore sia *. Listing -0 0 $rootnode ->children() ->arraynode('connection') ->children() ->scalarnode('driver') ->isrequired() ->cannotbeempty() ->end() ->scalarnode('host') ->defaultvalue('localhost') ->end() ->scalarnode('utente')->end() ->scalarnode('password')->end() ->booleannode('memory') ->defaultfalse() ->end() generated on August, 0 Chapter : Definire e processare valori di configurazione 0
31 0 0 ; ->end() ->end() ->arraynode('settings') ->adddefaultsifnotset() ->children() ->scalarnode('nome') ->isrequired() ->cannotbeempty() ->defaultvalue('valore') ->end() ->end() ->end() ->end() Sezioni facoltative Se si hanno intere sezioni facoltative e che possono essere abilitate/disabilitate, si possono sfruttare le scorciatoie canbeenabled() e canbedisabled() : Listing - 0 $arraynode ->canbeenabled() ; // è equivalente a $arraynode ->treatfalselike(array('enabled' => false)) ->treattruelike(array('enabled' => true)) ->treatnulllike(array('enabled' => true)) ->children() ->booleannode('enabled') ->defaultfalse() ; Il metodo canbedisabled è uguale, tranne per il fatto che la sezione viene abilitata in modo predefinito. Opzioni di fusione Si possono fornire opzioni aggiuntive sul processo di fusione. Per gli array: performnodeepmerging() Quando il valore è definito anche in un altro array di configurazione, non provare a fondere un array, ma sovrascrivilo completamente Per tutti i nodi: cannotbeoverwritten() non consentire che altri array di configurazione sovrascrivano il valore di questo nodo. generated on August, 0 Chapter : Definire e processare valori di configurazione
32 Aggiunta di sezioni Se occorre validare una configurazione complessa, l'albero potrebbe diventare troppo grande, si potrebbe quindi volerlo separare in sezioni. Lo si può fare creando una sezione come nodo separato e quindi aggiungendola all'albero principale con append(): Listing public function getconfigtreebuilder() $treebuilder = new TreeBuilder(); $rootnode = $treebuilder->root('database'); $rootnode ->children() ->arraynode('connection') ->children() ->scalarnode('driver') ->isrequired() ->cannotbeempty() ->end() ->scalarnode('host') ->defaultvalue('localhost') ->end() ->scalarnode('utente')->end() ->scalarnode('password')->end() ->booleannode('memory') ->defaultfalse() ->end() ->end() ->append($this->addparametersnode()) ->end() ->end() ; return $treebuilder; public function addparametersnode() $builder = new TreeBuilder(); $node = $builder->root('parameters'); $node ->isrequired() ->requiresatleastoneelement() ->useattributeaskey('nome') ->prototype('array') ->children() ->scalarnode('valore')->isrequired()->end() ->end() ->end() ; return $node; Questo è utile per evitare di ripetersi, nel caso in cui si abbiano sezioni della configurazione ripetute in posti diversi. generated on August, 0 Chapter : Definire e processare valori di configurazione
33 Normalizzazione Prima di essere processati, i file di configurazione vengono normalizzati, quindi fusi e infine si usa l'albero per validare l'array risultante. Il processo di normalizzazione si usa per rimuovere alcune differenze risultati dai vari formati di configurazione, soprattutto tra Yaml e XML. Il separatore usato nelle chiavi è tipicamente _ in Yaml e - in XML. Per esempio, auto_connect in Yaml e auto-connect. La normalizzazione rende entrambi auto_connect. La chiave interessata non sarà alterata se è mista, come pippo-pluto_muu, o se esiste già. Un'altra differenza tra Yaml e XML è il modo in cui sono rappresentati array di dati. In YAML si può avere: Listing - twig: extensions: ['twig.extension.pippo', 'twig.extension.pluto'] e in XML: Listing - <twig:config> <twig:extension>twig.extension.pippo</twig:extension> <twig:extension>twig.extension.pluto</twig:extension> </twig:config> La normalizzazione rimuove tale differenza, pluralizzando la chiave usata in XML. Si può specificare se si vuole una chiave pluralizzata in tal modo con fixxmlconfig(): Listing - $rootnode ->fixxmlconfig('extension') ->children() ->arraynode('extensions') ->prototype('scalar')->end() ->end() ->end() ; Se la pluralizzazione è irregolare, si può specificare il plurale da usare, come secondo parametro: Listing - $rootnode ->fixxmlconfig('uovo', 'uova') ->children() ->arraynode('uova') ->end() ->end() ; Oltre a sistemare queste cose, fixxmlconfig si assicura che i singoli elementi xml siano modificati in array. Quindi si potrebbe avere: Listing - <connessione>predefinito</connessione> <connessione>extra</connessione> generated on August, 0 Chapter : Definire e processare valori di configurazione
34 e a volte solo: Listing - <connessione>default</connessione> Per impostazione predefinita, connessione sarebbe un array nel primo caso e una stringa nel secondo, rendendo difficile la validazione. Ci si può assicurare che sia sempre un array con fixxmlconfig. Se necessario, si può controllare ulteriormente il processo di normalizzazione. Per esempio, si potrebbe voler consentire che una stringa sia impostata e usata come chiave particolare o che che molte chiavi siano impostate in modo esplicito. Quindi, se tutto tranne id è facoltativo, in questa configurazione: Listing - connessione: name: connessione_mysql host: localhost driver: mysql username: utente password: pass si può consentire anche il seguente: Listing -0 connection: my_mysql_connection Cambiando un valore stringa in un array associativo con name come chiave: Listing - 0 $rootnode ->children() ->arraynode('connessione') ->beforenormalization() ->ifstring() ->then(function($v) return array('name'=> $v); ) ->end() ->children() ->scalarnode('name')->isrequired() ->end() ->end() ->end() ; Regole di validazione Si possono fornire regole di validazione avanzata, usando ExprBuilder. Questa classe implementa un'interfaccia fluida per una struttura di controllo nota. Si può usare per aggiungere regole di validazione avanzate alle definizioni dei nodi, come: Listing - $rootnode ->children() ->arraynode('connessione') ->children() ->scalarnode('driver') ->isrequired() ->validate(). generated on August, 0 Chapter : Definire e processare valori di configurazione
35 0 ; ->ifnotinarray(array('mysql', 'sqlite', 'mssql')) ->theninvalid('valore non valido "%s"') ->end() ->end() ->end() ->end() ->end() Una regola di validazione ha sempre una parte "if". Si può specificare tale parte nel modo seguente: iftrue() ifstring() ifnull() ifarray() ifinarray() ifnotinarray() always() Una regola di validazione richiede anche una parte "then": then() thenemptyarray() theninvalid() thenunset() Di solito, "then" è una closure. Il suo valore di ritorno sarà usato come nuovo valore del nodo, al posto del valore originale del nodo. Processare i valori di configurazione La classe Processor usa l'albero, costruito usando TreeBuilder 0, per processare molteplici array di valori di configurazione da fondere. Se un valore non è del tipo atteso, è obbligatorio e non ancora definito oppure non può essere validato in altri modi, sarà lanciata un'eccezione. Altrimenti, il risultato è un array pulito di valori di configurazione: Listing - 0 use Symfony\Component\Yaml\Yaml; use Symfony\Component\Config\Definition\Processor; use Acme\DatabaseConfiguration; $config = Yaml::parse( file_get_contents( DIR.'/src/Matthias/config/config.yml') ); $config = Yaml::parse( file_get_contents( DIR.'/src/Matthias/config/config_extra.yml') ); $configs = array($config, $config); $processor = new Processor(); $configuration = new DatabaseConfiguration(); $processedconfiguration = $processor->processconfiguration( generated on August, 0 Chapter : Definire e processare valori di configurazione
36 ); $configuration, $configs generated on August, 0 Chapter : Definire e processare valori di configurazione
37 Chapter Il componente Console Il componente Console semplifica la creazione di eleganti e testabili comandi da terminale. Symfony viene distribuito con un componente Console, che permette di creare comandi da terminale. I comandi da terminale possono essere utilizzati per qualsiasi lavoro ripetitivo, come i lavori di cron, importazioni o lavori batch. Installazione Il componente può essere installato in due modi: Installandolo tramite Composer (symfony/console su Packagist ); Utilizzando il repository Git ufficiale ( ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Creazione di comandi di base Per creare un comando che porga il saluto dal terminale, creare il file SalutaCommand.php, contenente il seguente codice: Listing - namespace Acme\Console\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument;. generated on August, 0 Chapter : Il componente Console
38 use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class SalutaCommand extends Command protected function configure() $this ->setname('demo:saluta') ->setdescription('saluta qualcuno') ->addargument( 'nome', InputArgument::OPTIONAL, 'Chi vuoi salutare?' ) ->addoption( 'urla', null, InputOption::VALUE_NONE, 'Se impostato, il saluto verrà urlato con caratteri maiuscoli' ) ; protected function execute(inputinterface $input, OutputInterface $output) $nome = $input->getargument('nome'); if ($nome) $testo = 'Ciao '.$nome; else $testo = 'Ciao'; if ($input->getoption('urla')) $testo = strtoupper($testo); $output->writeln($testo); Occorre anche creare il file da eseguire in linea di comando, che crea una Application e vi aggiunge comandi: Listing - 0 #!/usr/bin/env php <?php // application.php require DIR.'/vendor/autoload.php'; use Acme\Console\Command\SalutaCommand; use Symfony\Component\Console\Application; $application = new Application(); $application->add(new SalutaCommand()); $application->run(); È possibile provare il programma nel modo seguente generated on August, 0 Chapter : Il componente Console
39 Listing - $ php application.php demo:saluta Fabien Il comando scriverà, nel terminale, quello che segue: Listing - Ciao Fabien È anche possibile usare l'opzione --urla per stampare il saluto in lettere maiuscole: Listing - $ php application.php demo:saluta Fabien --urla Il cui risultato sarà: Listing - CIAO FABIEN Colorare l'output Windows non supporta i colori ANSI in modo predefinito, quindi il componente Console individua e disabilita i colori quando Windows non dà supporto. Tuttavia, se Windows non è configurato con un driver ANSI e i propri comandi di console invocano altri script che emettono sequenze di colori ANSI, saranno mostrati come sequenze di caratteri grezzi. Per abilitare il supporto ai colori ANSI su Windows, si può installare ConEmu o ANSICON. È possibile inserire il testo da stampare, all'interno di speciali tag per colorare l'output. Ad esempio: Listing - 0 // testo verde $output->writeln('<info>pippo</info>'); // testo giallo $output->writeln('<comment>pippo</comment>'); // testo nero su sfondo ciano $output->writeln('<question>pippo</question>'); // testo nero su sfondo rosso $output->writeln('<error>pippo</error>'); Si può definire un proprio stile, usando la classe OutputFormatterStyle : Listing - use Symfony\Component\Console\Formatter\OutputFormatterStyle; $style = new OutputFormatterStyle('red', 'yellow', array('bold', 'blink')); $output->getformatter()->setstyle('fire', $style); $output->writeln('<fire>pippo</fire>'); I colori di sfondo e di testo disponibili sono: black, red, green, yellow, blue, magenta, cyan e white. Le opzioni disponibili sono: bold, underscore, blink, reverse e conceal. Si possono anche impostare colori e opzioni dentro il tag: generated on August, 0 Chapter : Il componente Console
40 Listing - // testo verde $output->writeln('<fg=green>pippo</fg=green>'); // testo nero su sfondo ciano $output->writeln('<fg=black;bg=cyan>pippo</fg=black;bg=cyan>'); // testo grassetto su sfondo giallo $output->writeln('<bg=yellow;options=bold>pippo</bg=yellow;options=bold>'); Livelli di verbosità New in version.: Le costanti VERBOSITY_VERY_VERBOSE e VERBOSITY_DEBUG sono state introdotte nella versione. La console dispone di tre livelli di verbosità. Tali livelli sono definiti in OutputInterface : Opzione OutputInterface::VERBOSITY_QUIET OutputInterface::VERBOSITY_NORMAL OutputInterface::VERBOSITY_VERBOSE OutputInterface::VERBOSITY_VERY_VERBOSE OutputInterface::VERBOSITY_DEBUG Valore Nessun messaggio in output Livello predefinito di verbosità Verbosità maggiore Messaggi informativi non essenziali Messaggi di debug Si può specificare il livello quieto di verbosità con l'opzione --quiet o -q. L'opzione --verbose o -v si usa quando si vuole un livello di verbosità maggiore. Se si usa il livello VERBOSITY_VERBOSE, viene mostrato lo stacktrace completo delle eccezioni. È anche possibile mostrare un messaggio in un comando solo per uno specifico livello di verbosità. Per esempio: Listing -0 if (OutputInterface::VERBOSITY_VERBOSE <= $output->getverbosity()) $output->writeln(...); Ci sono anche metodi più semantici da usare, per testare ciascun livello di verbosità: Listing - 0 if ($output->isquiet()) if ($output->isverbose()) if ($output->isveryverbose()). generated on August, 0 Chapter : Il componente Console 0
41 if ($output->isdebug()) Quando si usa il livello quieto, viene soppresso ogni output, poiché il metodo write() esce senza stampare nulla. MonologBridge fornisce una classe ConsoleHandler, che consente di mostrare messaggi sulla console. Questo è un modo più pulito rispetto a inserire le chiamate di output all'interno di condizioni. Per un esempio di utilizzo nel framework Symfony, vedere Configurare Monolog per mostrare messaggi di console. Utilizzo dei parametri nei comandi La parte più interessante dei comandi è data dalla possibilità di mettere a disposizione parametri e opzioni. I parametri sono delle stringhe, separate da spazi, che seguono il nome stesso del comando. Devono essere inseriti in un ordine preciso e possono essere opzionali o obbligatori. Ad esempio, per aggiungere un parametro opzionale cognome al precedente comando e rendere il parametro nome obbligatorio, si dovrà scrivere: Listing - 0 $this ->addargument( 'nome', InputArgument::REQUIRED, 'Chi vuoi salutare?' ) ->addargument( 'cognome', InputArgument::OPTIONAL, 'Il tuo cognome?' ); A questo punto si può accedere al parametro cognome dal codice: Listing - if ($cognome = $input->getargument('cognome')) $testo.= ' '.$cognome; Il comando potrà essere utilizzato in uno qualsiasi dei seguenti modi: Listing - $ php application.php demo:saluta Fabien $ php application.php demo:saluta Fabien Potencier È anche possibile consentire una lista di valori a un parametro (si immagini di voler salutare tutti gli amici). Lo si deve fare alla fine della lista dei parametri: Listing generated on August, 0 Chapter : Il componente Console
42 $this ->addargument( 'nomi', InputArgument::IS_ARRAY, 'Chi vuoi salutare (separare i nomi con uno spazio)?' ); In questo modo, si possono specificare più nomi: Listing - $ php application.php demo:saluta Fabien Ryan Bernhard Si può accedere al parametro nomi come un array: Listing - if ($nomi = $input->getargument('nomi')) $testo.= ' '.implode(', ', $nomi); Ci sono tre varianti di parametro utilizzabili: Modalità InputArgument::REQUIRED InputArgument::OPTIONAL InputArgument::IS_ARRAY Valore Il parametro è obbligatorio Il parametro è facoltativo, può essere omesso Il parametro può contenere un numero indefinito di parametri e deve essere usato alla fine della lista dei parametri Si può combinare IS_ARRAY con REQUIRED e OPTIONAL, per esempio: Listing - $this ->addargument( 'nomi', InputArgument::IS_ARRAY InputArgument::REQUIRED, 'Chi vuoi salutare (separare i nomi con uno spazio)?' ); Utilizzo delle opzioni nei comandi Diversamente dagli argomenti, le opzioni non sono ordinate (cioè possono essere specificate in qualsiasi ordine) e sono identificate dal doppio trattino (come in --urla; è anche possibile dichiarare una scorciatoia a singola lettera preceduta da un solo trattino come in -u). Le opzioni sono sempre opzionali e possono accettare valori (come in --dir=src) o essere semplici indicatori booleani senza alcuna assegnazione (come in --urla). Nulla impedisce la creazione di un comando con un'opzione che accetti in modo facoltativo un valore. Tuttavia, non c'è modo di distinguere quando l'opzione sia stata usata senza un valore (comando --urla) o quando non sia stata usata affatto (comando). In entrambi i casi, il valore recuperato per l'opzione sarebbe null. generated on August, 0 Chapter : Il componente Console
43 Ad esempio, per specificare il numero di volte in cui il messaggio di saluto sarà stampato, si può aggiungere la seguente opzione: Listing - $this ->addoption( 'ripetizioni', null, InputOption::VALUE_REQUIRED, 'Quante volte dovrà essere stampato il messaggio?', ); Ora è possibile usare l'opzione per stampare più volte il messaggio: Listing -0 for ($i = 0; $i < $input->getoption('ripetizioni'); $i++) $output->writeln($testo); In questo modo, quando si esegue il comando, sarà possibile specificare, opzionalmente, l'impostazione --ripetizioni: Listing - $ php application.php demo:saluta Fabien $ php application.php demo:saluta Fabien --ripetizioni= Nel primo esempio, il saluto verrà stampato una sola volta, visto che ripetizioni è vuoto e il suo valore predefinito è (l'ultimo parametro di addoption). Nel secondo esempio, il saluto verrà stampato volte. Ricordiamo che le opzioni non devono essere specificate in un ordine predefinito. Perciò, entrambi i seguenti esempi funzioneranno correttamente: Listing - $ php application.php demo:saluta Fabien --ripetizioni= --urla $ php application.php demo:saluta Fabien --urla --ripetizioni= Ci sono possibili varianti per le opzioni: Opzione Valore InputOption::VALUE_IS_ARRAY Questa opzione accetta valori multipli (p.e. --dir=/pippo -- dir=/pluto) InputOption::VALUE_NONE Non accettare alcun valore per questa opzione (come in -- urla) InputOption::VALUE_REQUIRED InputOption::VALUE_OPTIONAL Il valore è obbligatorio (come in ripetizioni=), l'opzione stessa è comunque facoltativa L'opzione può avere un valore o meno (p.e. urla o urla=forte) È possibile combinare VALUE_IS_ARRAY con VALUE_REQUIRED o con VALUE_OPTIONAL nel seguente modo: Listing - $this ->addoption( 'ripetizioni', null, generated on August, 0 Chapter : Il componente Console
44 ); InputOption::VALUE_REQUIRED InputOption::VALUE_IS_ARRAY, 'Quante volte dovrà essere stampato il messaggio?', Aiutanti di console Il componente Console contiene anche una serie di "aiutanti", vari piccoli strumenti in grado di aiutare con diversi compiti: Aiutante Dialog: chiede informazioni interattive all'utente Aiutante Formatter: personalizza i colori dei testi Aiutante Progress: mostra una barra di progressione Aiutante Table: mostra dati in una tabella Testare i comandi Symfony mette a disposizione diversi strumenti a supporto del test dei comandi. Il più utile di questi è la classe CommandTester. Questa utilizza particolari classi per la gestione dell'input/output che semplificano lo svolgimento di test senza una reale interazione da terminale: Listing use Acme\Command\SalutaCommand; use Symfony\Component\Console\Application; use Symfony\Component\Console\Tester\CommandTester; class ListCommandTest extends \PHPUnit_Framework_TestCase public function testexecute() $application = new Application(); $application->add(new SalutaCommand()); $comando = $application->find('demo:saluta'); $testdelcomando = new CommandTester($comando); $testdelcomando->execute(array('command' => $comando->getname())); $this->assertregexp('/.../', $testdelcomando->getdisplay()); Il metodo getdisplay() 0 restituisce ciò che sarebbe stato mostrato durante una normale chiamata dal terminale. Si può testare l'invio di argomenti e opzioni al comando, passandoli come array al metodo execute() : Listing - use Acme\Command\SalutaCommand; use Symfony\Component\Console\Application; generated on August, 0 Chapter : Il componente Console
45 0 0 use Symfony\Component\Console\Tester\CommandTester; class ListCommandTest extends \PHPUnit_Framework_TestCase public function testnameisoutput() $application = new Application(); $application->add(new SalutaCommand()); $comando = $application->find('demo:saluta'); $testdelcomando = new CommandTester($command); $testdelcomando->execute(array( 'command' => $comando->getname(), 'nome' => 'Fabien', '--ripetizioni' =>, )); $this->assertregexp('/fabien/', $testdelcomando->getdisplay()); È possibile testare un'intera applicazione da terminale utilizzando ApplicationTester. Richiamare un comando esistente Se un comando dipende da un altro, da eseguire prima, invece di chiedere all'utente di ricordare l'ordine di esecuzione, lo si può richiamare direttamente. Questo è utile anche quando si vuole creare un "meta" comando, che esegue solo una serie di altri comandi (per esempio, tutti i comandi necessari quando il codice del progetto è cambiato sui server di produzione: pulire la cache, generare i proxy di Doctrine, esportare le risorse di Assetic,...). Richiamare un comando da un altro è molto semplice: Listing - 0 protected function execute(inputinterface $input, OutputInterface $output) $comando = $this->getapplication()->find('demo:saluta'); $parametri = array( 'command' => 'demo:saluta', 'nome' => 'Fabien', '--urla' => true, ); $input = new ArrayInput($parametri); $codicediritorno = $comando->run($input, $output);. generated on August, 0 Chapter : Il componente Console
46 Innanzitutto si dovrà trovare (find() ) il comando da eseguire usandone il nome come parametro. Quindi si dovrà creare un nuovo ArrayInput che contenga i parametri e le opzioni da passare al comando. Infine, la chiamata al metodo run() manderà effettivamente in esecuzione il comando e restituirà il codice di ritorno del comando (0 se tutto è andato a buon fine, un qualsiasi altro intero negli altri altri casi). Nella maggior parte dei casi, non è una buona idea quella di eseguire un comando al di fuori del terminale. Innanzitutto perché l'output del comando è ottimizzato per il terminale. Ma, anche più importante, un comando è come un controllore: dovrebbe usare un modello per fare qualsiasi cosa e restituire informazioni all'utente. Perciò, invece di eseguire un comando dal Web, sarebbe meglio provare a rifattorizzare il codice e spostare la logica all'interno di una nuova classe. Saperne di più Uso di Console Come costruire un'applicazione in un singolo comando Cambiare comando predefinito Usare gli eventi Capire come sono gestiti i parametri della console. generated on August, 0 Chapter : Il componente Console
47 Chapter Uso di Console Oltre alle opzioni specificate per i comandi, ci sono anche alcune opzioni predefinite, oltre che alcuni comandi predefiniti per il componente Console. Questi esempi ipotizzano che sia stato aggiunto un file application.php, da eseguire dalla linea di comando: Listing - #!/usr/bin/env php <?php // application.php use Symfony\Component\Console\Application; $application = new Application(); $application->run(); Comandi predefiniti C'è un comando list, che mostra tutti i comandi registrati e le opzioni disponibili: Listing - $ php application.php list Si può ottenere lo stesso risultato, non eseguendo alcun comando Listing - $ php application.php Il comando help elenca le informazioni di aiuto per il comando specificato. Per esempio, per ottenere aiuto sul comando list: Listing - $ php application.php help list generated on August, 0 Chapter : Uso di Console
48 Eseguendo help senza specificare alcun comando mostrerà le opzioni globali: Listing - $ php application.php help Opzioni globali Si possono ottenere informazioni per ogni comando, con l'opzione --help. Per ottenere aiuto per il comando list: Listing - $ php application.php list --help $ php application.php list -h Si può avere in elenco meno verboso con: Listing - $ php application.php list --quiet $ php application.php list -q Si possono ottenere messaggi più verbosi (se supportato dal comando) con: Listing - $ php application.php list --verbose $ php application.php list -v Le opzioni sulla verbosità hanno un parametro opzionale, tra (predefinito) e, per mostrare messaggi ancora più verbosi: Listing - $ php application.php list --verbose= $ php application.php list -vv $ php application.php list --verbose= $ php application.php list -vvv Se si impostano, in modo facoltativo, nome e versione dell'applicazione: Listing -0 $application = new Application('Applicazione Acme Console', '.'); si può usare: Listing - $ php application.php list --version $ php application.php list -V per ottenere queste informazioni: Listing - Applicazione Acme Console version. Se non si forniscono entrambi i parametri, si otterrà solamente: Listing - console tool Si può forzare la colorazione ANSI con: Listing - $ php application.php list --ansi generated on August, 0 Chapter : Uso di Console
49 o disattivarla con: Listing - $ php application.php list --no-ansi Si possono aggirare le domande interattive del comando in esecuzione con: Listing - $ php application.php list --no-interaction $ php application.php list -n Sintassi breve Non occorre scrivere i nomi interi dei comandi. Basta scrivere la più breve parte non ambigua di un comando, per eseguirlo. Quindi, se non ci sono comandi con un nome simile, si può richiamare help in questo modo: Listing - $ php application.php h Se si hanno comandi che usano : per gli spazi dei nomi, occorre scrivere un pezzo di testo non ambiguo per ogni parte. Se è stato creato il comando demo:saluta, come mostrato in Il componente Console, lo si può eseguire con: Listing - $ php application.php d:s Fabien Se si sceglie un comando troppo breve e quindi ambiguo (cioè più di un comando corrisponde), non verrà eseguito alcun comando, ma verranno mostrati dei suggerimenti sui possibili comandi da eseguire. generated on August, 0 Chapter : Uso di Console
50 Chapter Cambiare comando predefinito New in version.: Il metodo setdefaultcommand() è stato introdotto in Symfony.. eseguirà sempre ListCommand se non si passa un nome di comando. Per cambiare il comando predefinito, basta passare il nome del comando che si vuole eseguire al metodo setdefaultcommand: Listing - 0 namespace Acme\Console\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class HelloWorldCommand extends Command protected function configure() $this->setname('ciao:mondo') ->setdescription('mostra \'Ciao mondo\''); protected function execute(inputinterface $input, OutputInterface $output) $output->writeln('ciao mondo'); Eseguire l'applicazione e cambiare il comando predefinito: Listing - // application.php use Acme\Console\Command\HelloWorldCommand; use Symfony\Component\Console\Application; $command = new HelloWorldCommand(); $application = new Application();. generated on August, 0 Chapter : Cambiare comando predefinito 0
51 0 $application->add($command); $application->setdefaultcommand($command->getname()); $application->run(); Verificare il nuovo comando predefinito, eseguendo: Listing - $ php application.php Mostrerà il seguente: Listing - Ciao Fabien Questa caratteristica ha una limitazione: non la si può usare con i parametri dei comandi. Saperne di più Come costruire un'applicazione in un singolo comando generated on August, 0 Chapter : Cambiare comando predefinito
52 Chapter Come costruire un'applicazione in un singolo comando Quando si costruisce uno strumento a linea di comando, potrebbe non essere necessario fornire molti comandi. In questo caso, dover passare ogni volta il nome del comando potrebbe essere noioso. Per fortuna, è possibile rimuovere questo obbligo, estendendo l'applicazione: Listing namespace Acme\Tool; use Symfony\Component\Console\Application; use Symfony\Component\Console\Input\InputInterface; class MiaApplicazione extends Application /** * Restituisce il nome del comando, in base all'input * InputInterface $input L'interfaccia input * string Il nome del comando */ protected function getcommandname(inputinterface $input) // Deve restituire il nome del comando return 'mio_comando'; /** * Restituice i comandi predefiniti, che sono sempre disponibili * array Un array di comandi predefiniti */ protected function getdefaultcommands() // Mantenere i comandi del nucleo, per avere HelpCommand, // usato quando si passa l'opzione --help generated on August, 0 Chapter : Come costruire un'applicazione in un singolo comando
53 0 0 $defaultcommands = parent::getdefaultcommands() $defaultcommands[] = new MioComando(); return $defaultcommands; /** * Sovrascritto in modo che l'applicazione non si aspetti il nome del * comando come primo parametro. */ public function getdefinition() $inputdefinition = parent::getdefinition(); // pulisce il primo parametro normale, che è il nome del comando $inputdefinition->setarguments(); return $inputdefinition; Richiamando lo script da console, sarà sempre usato il comando MioComando, senza doverne passare il nome. Si può anche semplificare come eseguire l'applicazione: Listing - #!/usr/bin/env php <?php // command.php use Acme\Tool\MiaApplicazione; $applicazione = new MiaApplicazione(); $applicazione->run(); generated on August, 0 Chapter : Come costruire un'applicazione in un singolo comando
54 Chapter Capire come sono gestiti i parametri della console Potrebbe risultare difficile capire il modo in cui le applicazione di console gestiscono i parametri. Un'applicazione di console Symony, come molti altri strumenti di CLI, segue il comportamento descritto negli standard docopt. Diamo uno sguardo al seguente comando, che ha tre opzioni: Listing namespace Acme\Console\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class DemoArgsCommand extends Command protected function configure() $this ->setname('demo:args') ->setdescription('descrive i comportamenti dei parametri') ->setdefinition( new InputDefinition(array( new InputOption('foo', 'f'), new InputOption('bar', 'b', InputOption::VALUE_REQUIRED), new InputOption('cat', 'c', InputOption::VALUE_OPTIONAL), )) ); protected function execute(inputinterface $input, OutputInterface $output). generated on August, 0 Chapter : Capire come sono gestiti i parametri della console
55 Poiché l'opzione foo non accetta valori, sarà o false (se non passata al comando) o true (se l'utente passa l'opzione --foo). Il valore dell'opzione bar (o della scorciatoia b) è obbligatorio. Può essere separato dal nome dell'opzione da spazi o dal carattere =. L'opzione cat (e la sua scorciatoia c) si comporta in modo simile, ma non ha un valore obbligatorio. Uno sguardo alla seguente tabella offre una panoramica dei modi possibili di passare queste opzioni: Input foo bar cat --bar=hello false "Hello" null --bar Hello false "Hello" null -b=hello false "Hello" null -b Hello false "Hello" null -bhello false "Hello" null -fcworld -b Hello true "Hello" "World" -cfworld -b Hello false "Hello" "fworld" -cbworld false null "bworld" Le cose si complicano un po' se il comando accetta anche un parametro facoltativo: Listing - new InputDefinition(array( new InputArgument('arg', InputArgument::OPTIONAL), )); Si potrebbe dover usare il separatore -- per separare le opzioni dai parametri. Diamo uno sguardo al quinto esempio nella tabella seguente, in cui è usato per dire al comando che World è il valore di arg e non il valore dell'opzione facoltativa cat: Input bar cat arg --bar Hello "Hello" null null --bar Hello World "Hello" null "World" --bar "Hello World" "Hello World" null null --bar Hello --cat World "Hello" "World" null --bar Hello --cat -- World "Hello" null "World" -b Hello -c World "Hello" "World" null generated on August, 0 Chapter : Capire come sono gestiti i parametri della console
56 Chapter Usare gli eventi New in version.: Gli eventi della console sono stati aggiunti in Symfony.. La classe Application del componente Console consente di agganciarsi al ciclo di vita di un'applicazione console, tramite gli eventi. Invece di reinventare la ruota, usa il componente EventDispatcher di Symfony: Listing - use Symfony\Component\Console\Application; use Symfony\Component\EventDispatcher\EventDispatcher; $dispatcher = new EventDispatcher(); $application = new Application(); $application->setdispatcher($dispatcher); $application->run(); Gli eventi della Console scattano solo quando viene eseguito il comando principale. I comandi richiamati dal comando principale non faranno scattare eventi. L'evento ConsoleEvents::COMMAND Scopi tipici: fare qualcosa prima che i comandi siano eseguiti (come scrivere nel log quale comando sta per essere eseguito) o mostrare qualcosa sull'evento che sta per essere eseguito. L'evento ConsoleEvents::COMMAND è distribuito prima dell'esecuzione dei comandi. Gli ascoltatori ricevono un evento ConsoleCommandEvent : Listing - use Symfony\Component\Console\Event\ConsoleCommandEvent; use Symfony\Component\Console\ConsoleEvents; $dispatcher->addlistener(consoleevents::command, function (ConsoleCommandEvent $event). generated on August, 0 Chapter : Usare gli eventi
57 0 // prende l'istanza di input $input = $event->getinput(); // prende l'istanza di output $output = $event->getoutput(); // prende il comando da eseguire $command = $event->getcommand(); // scrive qualcosa sul comando $output->writeln(sprintf('prima di eseguire il comando <info>%s</info>', $command->getname())); ); // prende l'applicazione $application = $command->getapplication(); L'evento ConsoleEvents::TERMINATE Scopi tipici: eseguire delle azioni di pulizia dopo l'esecuzione di un comando. L'evento ConsoleEvents::TERMINATE è distribuito dopo l'esecuzione di un comando. Può essere usato per qualsiasi azione che debba essere eseguita per tutti i comandi o per pulire qualcosa iniziato in un ascoltatore ConsoleEvents::COMMAND (come inviare log, chiudere connessioni a basi dati, inviare ,...). Un ascoltatore potrebbe anche cambiare il codice di uscita. Gli ascoltatori ricevono un evento ConsoleTerminateEvent : Listing - 0 use Symfony\Component\Console\Event\ConsoleTerminateEvent; use Symfony\Component\Console\ConsoleEvents; $dispatcher->addlistener(consoleevents::terminate, function (ConsoleTerminateEvent $event) // prende l'output $output = $event->getoutput(); // prende il comando che è stato eseguito $command = $event->getcommand(); // mostra qualcosa $output->writeln(sprintf('dopo l\'esecuzione del comando <info>%s</info>', $command->getname())); ); // cambia il codice di uscita $event->setexitcode(); Questo evento viene distribuito anche se il comando lancia un'eccezione. Viene distribuito appena prima dell'evento ConsoleEvents::EXCEPTION. In questo caso, il codice di uscita ricevuto è il codice dell'eccezione.. generated on August, 0 Chapter : Usare gli eventi
58 L'evento ConsoleEvents::EXCEPTION Scopi tipici: gestire le eccezioni sollevate durante l'esecuzione di un comando. Ogni volta che un comando solleva un'eccezione, viene distribuito l'evento ConsoleEvents::EXCEPTION. Un ascoltatore può avvolgere o modificare l'eccezione o fare qualcosa di utile che l'applicazione lanci l'eccezione. Gli ascoltatori ricevono un evento ConsoleExceptionEvent : Listing - 0 use Symfony\Component\Console\Event\ConsoleExceptionEvent; use Symfony\Component\Console\ConsoleEvents; $dispatcher->addlistener(consoleevents::exception, function (ConsoleExceptionEvent $event) $output = $event->getoutput(); $command = $event->getcommand(); $output->writeln(sprintf('oops, eccezione lanciat durante l'\esecuzione del comando <info>%s</info>', $command->getname())); // prende il codice di uscita (il codice dell'eccezione o il codice di uscita impostato da un evento ConsoleEvents::TERMINATE) $exitcode = $event->getexitcode(); // cambia l'eccezione con un'altra $event->setexception(new \LogicException('Eccezione', $exitcode, $event->getexception())); );. generated on August, 0 Chapter : Usare gli eventi
59 Chapter Uso del Logger New in version.: ConsoleLogger è stato introdotto in Symfony.. Il componente Console dispone di un logger, che aderisce allo standard PSR-. A seconda dell'impostazione sulla verbosità, i messaggi di log saranno inviati all'istanza di OutputInterface passata come parametro al costruttore. Il logger non ha dipendenze esterno, tranne php-fig/log. Questo è utile per applicazioni e comandi di console che vogliono un logger PSR- leggero: Listing - 0 namespace Acme; use Psr\Log\LoggerInterface; class MyDependency private $logger; public function construct(loggerinterface $logger) $this->logger = $logger; public function dostuff() $this->logger->info('mi piacciono i tagli di Tony Vairelles.'); Ci si può appoggiare al logger per usare questa dipendenza da dentro un comando: Listing - namespace Acme\Console\Command; generated on August, 0 Chapter : Uso del Logger
60 0 0 use Acme\MyDependency; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Logger\ConsoleLogger; class MyCommand extends Command protected function configure() $this ->setname('mio:comando') ->setdescription( 'Usa una dipendenza esterna che richiede un logger PSR-' ) ; protected function execute(inputinterface $input, OutputInterface $output) $logger = new ConsoleLogger($output); $mydependency = new MyDependency($logger); $mydependency->dostuff(); La dipendenza userà l'istanza di ConsoleLogger come logger. I messaggi di log emessi saranno mostrati nell'output della console. Verbosità A seconda del livello di verbosità con cui è eseguito il comando, i messaggi potrebbero o meno essere inviati all'istanza di OutputInterface. I logger della console si comporta come il gestore di console di Monolog. L'associazione tra livello di log e verbosità può essere configurata tramite il secondo parametro del costruttore di ConsoleLogger : Listing - $verbositylevelmap = array( LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL, LogLevel::INFO => OutputInterface::VERBOSITY_NORMAL, ); $logger = new ConsoleLogger($output, $verbositylevelmap); Colori Il logger mostra messaggi di log formattati con un colore che ne riflette il livello, Questo comportamento è configurabile tramite il terzo parametro del costruttore: Listing generated on August, 0 Chapter : Uso del Logger 0
61 $formatlevelmap = array( LogLevel::CRITICAL => self::info, LogLevel::DEBUG => self::error, ); $logger = new ConsoleLogger($output, array(), $formatlevelmap); generated on August, 0 Chapter : Uso del Logger
62 Chapter 0 Aiutante Dialog L'aiutante Dialog è stato deprecato in Symfony. e sarà rimosso in Symfony.0. Usare invece l'aiutante Question, che è più facile da usare. DialogHelper fornisce funzioni per chiedere informazioni all'utente. È incluso nell'insieme predefinito degli aiutanti, ottenibile richiamando gethelperset() : Listing 0- $dialog = $this->gethelper('dialog'); Tutti i metodi all'interno dell'aiutante Dialog hanno OutputInterface come primo parametro, la domanda come secondo parametro e il valore predefinito come ultimo parametro. Chiedere conferma all'utente Si supponga di voler confermare un'azione prima di eseguirla effettivamente. Aggiungere al comando il codice seguente: Listing 0- if (!$dialog->askconfirmation( $output, '<question>continuare con questa azione?</question>', false )) return; In questo caso, all'utente sarà chiesto "Continuare con questa azione", e si otterrà true se l'utente risponde y o false se l'utente risponde n. Il terzo parametro di askconfirmation() è il valore generated on August, 0 Chapter 0: Aiutante Dialog
63 predefinito da restituire se l'utente non inserisce alcun input. Qualsiasi altro input farà ripetere la domanda. Chiedere informazioni all'utente Si possono anche porre domande con risposte più complesse di sì/no. Per esempio, se si vuole sapere il nome di un bundle, si può aggiungere al comando: Listing 0- $bundle = $dialog->ask( $output, 'Prego inserire il nome del bundle', 'AcmeDemoBundle' ); All'utente sarà chiesto "Prego inserire il nome del bundle". L'utente potrà inserire un nome, che sarà restituito dal metodo ask(). Se lasciato vuoto, sarà restituito il valore predefinito (qui AcmeDemoBundle). Autcompletamento Si possono anche specificare delle risposte possibili alla domanda data. Saranno completate man mano che l'utente scrive: Listing 0- $dialog = $this->gethelper('dialog'); $bundlenames = array('acmedemobundle', 'AcmeBlogBundle', 'AcmeStoreBundle'); $name = $dialog->ask( $output, 'Prego inserire il nome del bundle', 'PippoBundle', $bundlenames ); Nascondere la risposta dell'utente Si può anche fare una domanda e nascodere la risposta. Ciò risulta utile in particolare per le password: Listing 0- $dialog = $this->gethelper('dialog'); $password = $dialog->askhiddenresponse( $output, 'Inserire la password della base dati', false ); Quando si richiede una risposta nascosta, Symfony userà un binario, cambierà la modalità stty oppure userà un altro trucco per nascondere la risposta. Se nessuna opzione è disponibile, si arrenderà e mostrerà la risposta, a meno che non si passi false come terzo parametro, come nell'esempio appena visto. In questo caso, sarà sollevata una RuntimeException generated on August, 0 Chapter 0: Aiutante Dialog
64 Validare la risposta Si può anche validare la risposta. Per esempio, nell'ultimo esempio è stato chiesto il nome di un bundle. Seguendo le convenzioni di Symfony, il nome dovrebbe avere il suffisso Bundle. Lo si può validare, usando il metodo askandvalidate() : Listing 0-0 $bundle = $dialog->askandvalidate( $output, 'Prego inserire il nome del bundle', function ($answer) if ('Bundle'!== substr($answer, -)) throw new \RuntimeException( 'Il nome del bundle deve avere \'Bundle\' come suffisso' ); ); return $answer;, false, 'AcmeDemoBundle' Il metodo ha due nuovi parametri. La sua firma completa è: Listing 0- askandvalidate( OutputInterface $output, string array $question, callback $validator, integer $attempts = false, string $default = null, array $autocomplete = null ) Il parametro $validator è un callback, che gestisce la validazione. Dovrebbe lanciare un'eccezione se qualcosa va storto. Il messaggio dell'eccezione è mostrato nella console, quindi è una buona pratica inserirvi delle informazioni rilevanti. Si può impostare il numero massimo di volte in cui fare la domanda, nel parametro $attempts. Una volta raggiunto tale numero, sarà usato il valore predefinito, fornito nell'ultimo parametro. Usando false si indica che il numero di tentativi è infinito. L'utente vedrà la domanda finché inserisce una risposta non valida e potrà procedere solo in caso di risposta valida. Nascondere la risposta dell'utente Si può anche fare una domanda e validare una risposta nascosta: Listing 0- $dialog = $this->gethelper('dialog'); $validator = function ($value) if ('' === trim($value)) throw new \Exception('La password non può essere vuota'); return $value;. generated on August, 0 Chapter 0: Aiutante Dialog
65 0 ; $password = $dialog->askhiddenresponseandvalidate( $output, 'Si prega di inserire la password', $validator, 0, false ); Se si vuole consentire che la risposta sia visibile, in caso non possa essere nascosta per qualche ragione, passare true come quinto parametro. Consentire una scelta da una lista di risposte Se si ha un insieme predefinito di risposte tra cui l'utente può scegliere, si può usare il metodo ask descritto in precedenza oppure, per assicurarsi che l'utente fornisca una risposta corretta, il metodo askandvalidate. Entrambi hanno lo svantaggio di costringere lo sviluppatore a gestire i valori non corretti da solo. Si può invece usare il metodo select(), che assicura che l'utente possa inserire solamente una stringa valida, da una lista predefinita: Listing 0-0 $dialog = $this->gethelper('dialog'); $colors = array('rosso', 'blu', 'giallo'); $color = $dialog->select( $output, 'Scegli il tuo colore preferito (predefinito: rosso)', $colors, 0 ); $output->writeln('hai scelto: '. $colors[$color]); fare qualcosa con il colore L'opzione selezionata come predefinita va fornita come quarto parametro. Il valore predefinito è null, che significa che nessuna opzione è predefinita. Se l'utente inserisce una stringa non valida, viene mostrato un errore e chiesto all'utente di fornire una nuova risposta, finché non ne inserisce una valida o raggiunge il numero massimo di tentativi (definibile nel quinto parametro). Il valore predefinito per i tentativi è false, che equivale a infiniti tentativi. Si può definire un messaggio di errore personalizzato nel sesto parametro. New in version.: Il supporto alla selezione multipla è stato aggiunto in Symfony.. Scelte multiple A volte si possono dare più risposte. DialogHelper lo supporta tramite l'uso di valori separati da virgole. Per abilitare questa possibilità, occorre impostare il settimo parametro a true: Listing 0-0 $selected = $dialog->select( $output,. generated on August, 0 Chapter 0: Aiutante Dialog
66 0 ); 'Scegli il tuo colore preferito (predefinito: rosso)', $colors, 0, false, 'Il valore "%s" non è valido', true // abilita la selezione multipla $selectedcolors = array_map(function($c) use ($colors) return $colors[$c];, $selected); $output->writeln( 'Hai scelto: '. implode(', ', $selectedcolors) ); Se ora l'utente inserisce,, il risultato sarà: Hai scelto: blu, giallo. Testare un comando con un input atteso Se si vuole scrivere un test per un comando che si aspetta un qualche tipo di input da linea di comando, occorre sovrascrivere HelperSet usato dal comando: Listing use Symfony\Component\Console\Helper\DialogHelper; use Symfony\Component\Console\Helper\HelperSet; public function testexecute() $commandtester = new CommandTester($command); $dialog = $command->gethelper('dialog'); $dialog->setinputstream($this->getinputstream("test\n")); // Equivale all'inserimento di "Test" e pressione di ENTER // Se occorre una conferma, va bene anche "yes\n" $commandtester->execute(array('command' => $command->getname())); // $this->assertregexp('/.../', $commandtester->getdisplay()); protected function getinputstream($input) $stream = fopen('php://memory', 'r+', false); fputs($stream, $input); rewind($stream); return $stream; Impostando il flusso di input di DialogHelper, si imita ciò che la console farebbe internamente con l'input dell'utente tramite cli. In questo modo, si può testare ogni interazione, anche complessa, passando un appropriato flusso di input. generated on August, 0 Chapter 0: Aiutante Dialog
67 Chapter Aiutante Formatter L'aiutante Formatter fornisce funzioni per formattare l'output con colori. Si possono fare cose più avanzate con questo aiutante rispetto a Colorare l'output. L'aiutante FormatterHelper è incluso nell'insieme predefinito di aiutanti, ottenibile richiamando gethelperset() : Listing - $formatter = $this->gethelperset()->get('formatter'); I metodi restituiscono una stringa, che solitamente sarà resa nella console, passandola al metodo OutputInterface::writeln. Scrivere messaggi in una sezione Symfony offre uno stile definito per mostrare un messaggio che appartenga a una "sezione". Mostra la sezione con un colore e contornata da parentesi e il messaggio a destra di essa. A meno del colore, appare in questo modo: Listing - [UnaSezione] Qui appare un messaggio correlato alla sezione Per riprodurre questo stile, si può usare il metodo formatsection() : Listing - $formattedline = $formatter->formatsection( 'UnaSezione', 'Qui appare un messaggio correlato alla sezione' ); $output->writeln($formattedline); generated on August, 0 Chapter : Aiutante Formatter
68 Scrivere messaggi in un blocco A volte si vuole poter mostrare un intero blocco di testo con un colore di sfondo. Symfony lo usa per mostrare messaggi di errore. Se si mostrano messaggi di errore su più linee in modo manuale, si noterà che lo sfondo ha lunghezza pari solo a ciascuna singola linea. Usare il metodo formatblock() per generare un blocco: Listing - $errormessages = array('errore!', 'Qualcosa è andato storto'); $formattedblock = $formatter->formatblock($errormessages, 'error'); $output->writeln($formattedblock); Come si può vedere, passando un array di messaggi al metodo formatblock(), si crea l'output desiderato. Se si passa true come terzo parametro, il blocco sarà formattato con più padding (una linea vuota sopra e una sotto ai messaggi e due spazi a sinistra e a destra). Lo "stile" esatto usato nel blocco dipende dallo sviluppatore. In tal caso, è stato usato lo stile predefinito error, ma ci sono altri stili e se ne possono creare di propri. Vedere Colorare l'output generated on August, 0 Chapter : Aiutante Formatter
69 generated on August, 0 Chapter : Aiutante Formatter
70 Chapter Barra di progressione New in version.: La barra di progressione è stata introdotta in Symfony., come sostituto dell'aiutante Progress. Durante l'esecuzione di comandi molto lunghi, può essere d'aiuto mostrare informazioni sull'avanzamento, man mano che il comando gira: Per mostrare dettagli sull'avanzamento, usare ProgressBar, passandogli un numero totale di unità, e avanzando la progressione durante i passi: Listing - 0 use Symfony\Component\Console\Helper\ProgressBar; // crea una nuova barra di progressione (0 unità) $progress = new ProgressBar($output, 0); // inizia la barra $progress->start(); $i = 0; while ($i++ < 0) fare qualcosa // avanzare la progressione di una unità $progress->advance(); // si può anche avanzare la progressione di più unità // $progress->advance();. generated on August, 0 Chapter : Barra di progressione 0
71 0 // assicura che la barra di progressione sia al 00% $progress->finish(); Invece di far avanzare la barra di un numero di passi (con il metodo advance() ), si può anche impostare la progressione corrente, richiamando il metodo setcurrent(). La barra di progressione funziona solo su piattaforme con supporto per i codici ANSI. Su altre piattaforme non verrà generato alcun output. Se non si conosce in anticipo il numero di passi, si può omettere il relativo parametro durante la creazione dell'istanza di ProgressBar : Listing - $progress = new ProgressBar($output); La progressione sarà quindi mostrata come un throbber: Listing - # passi non definiti (mostra un throbber) 0 [> ] [-----> ] [============================] # passi definiti 0/ [> ] 0% / [=========> ] % / [============================] 00% Non appena il comando finisce, non dimenticare di richiamare finish(), per assicurarsi che la barra di progressione venga aggiornata con il completamento al 00%. Se si vuole mostrare qualcosa durante l'esecuzione della barra di progressione, richiamare prima clear(). Successivamente, richiamare display() per mostrare nuovamente la barra di progressione. Personalizzazione della barra di progressione Formati predefiniti Le informazioni predefinite della barra di progressione dipendono dal livello di verbosità dell'instanza di OutputInterface: Listing generated on August, 0 Chapter : Barra di progressione
72 0 # OutputInterface::VERBOSITY_NORMAL (CLI senza opzioni di verbosità) 0/ [> ] 0% / [=========> ] % / [============================] 00% # OutputInterface::VERBOSITY_VERBOSE (-v) 0/ [> ] 0% sec / [=========> ] % sec / [============================] 00% sec # OutputInterface::VERBOSITY_VERY_VERBOSE (-vv) 0/ [> ] 0% sec / [=========> ] % sec / [============================] 00% sec # OutputInterface::VERBOSITY_DEBUG (-vvv) 0/ [> ] 0% sec/ sec.0 MB / [=========> ] % sec/ sec.0 MB / [============================] 00% sec/ sec.0 MB Se si richiama un comando con l'opzione -q, la barra di progressione non sarà mostrata. Invece di appoggioarsi al livello di verbosità del comando, si può anche forzare il formato tramite setformat(): Listing - $bar->setformat('verbose'); I formati predefiniti sono i seguenti: normal verbose very_verbose debug Se non si imposta il numero di passi della barra di progressione, usare le varianti _nomax: normal_nomax verbose_nomax very_verbose_nomax debug_nomax Formati personalizzati Invece di usare i formati predefiniti, se ne possono definire di propri: Listing - $bar->setformat('%bar%'); In questo modo si imposta il formato per mostrare solo la barra stessa: Listing - > =========> ============================ generated on August, 0 Chapter : Barra di progressione
73 Un formato per la barra di progressione è una stringa con determinati segnaposto (un nome racchiuso da simboli %). I segnaposto vengono sostituiti in base alla progressione corrente della barra. Ecco una lista di segnaposto predefiniti: current: il passo corrente; max: il numero massimo di passi (o 0, se non definito); bar: la barra stessa; percent: la percentuale di completamento (non disponibile se max non è definito); elapsed: il tempo trascorso dall'inizio della barra di progressione; remaining: il tempo restante al completamento (non disponibile se max non è definito);; estimated: il tempo stimato per il completamento (non disponibile se max non è definito);; memory: l'uso della memoria; message: il messaggio allegato alla barra di progressione. Per esempi, ecco come si può impostare il formato per essere uguale a quello debug: Listing - $bar->setformat(' %current%/%max% [%bar%] %percent:s%% %elapsed:s%/%estimated:-s% %memory:s%'); Notare la parte :s aggiunta ad alcuni segnaposto. Questo è il modo in cui si può alterare il modo in cui appare la barra (formattazione e allineamento). La parte dopo il simbolo : è usata per impostare il formato sprintf della stringa. Il segnaposto message è un po' speciale, perché lo si deve impostare manualmente: Listing - 0 $bar->setmessage('inizio'); $bar->start(); $bar->setmessage('in corso...'); $bar->advance(); $bar->setmessage('finito'); $bar->finish(); Invece di impostare il formato per una data istanza della barra di progressione, si possono anche definire formati globali: Listing -0 ProgressBar::setFormatDefinition('minimal', 'Progress: %percent%%'); $bar = new ProgressBar($output, ); $bar->setformat('minimal'); Questo codice definisce un nuovo formato, chiamato minimal, che si può usare per le barre di progressione: Listing - Progress: 0% Progress: % Progress: 00% Spesso è meglio ridefinire i formati predefiniti, invece di crearn di nuovi, perché questo consente la variazione automatica in base alla verbosità del comando. generated on August, 0 Chapter : Barra di progressione
74 Quando si definisce un nuovo stile, che contiene segnaposto disponibili solo quando il numero massimo di passi sia noto, si dovrebbe creare una variante _nomax: Listing - ProgressBar::setFormatDefinition('minimal', '%percent%% %remaining%'); ProgressBar::setFormatDefinition('minimal_nomax', '%percent%%'); $bar = new ProgressBar($output); $bar->setformat('minimal'); Quando si mostra la barra di progressione, il formato sarà impostato automaticamente a minimal_nomax se la barra non dispone del numero massimo di passi, come nell'esempio precedente. Un formato può contenere qualsiasi codice ANSI valido e può anche usare i modi specifici di Symfony per impostare i colori: Listing - ProgressBar::setFormatDefinition( 'minimal', '<info>%percent%</info>\0[m%\0[0m <fg=white;bg=blue>%remaining%</>' ); Un formato può estendersi per più righe. Questo torna molto utile qando si vogliono mostrare più informazioni contestuali, insieme alla barra di progressione (vedere l'esempio all'inizio di questo articolo). Impostazioni della barra Tra i segnaposto, bar è un po' speciale, perché tutti i caratteri usati per mostrarlo possono essere personalizzati: Listing - 0 // la parte finita della barra $progress->setbarcharacter('<comment>=</comment>'); // la parte non finita della barra $progress->setemptybarcharacter(' '); // il carattere di progressione $progress->setprogresscharacter(' '); // la larghezza della barra $progress->setbarwidth(0); generated on August, 0 Chapter : Barra di progressione
75 Per questioni prestazionali, fare attenzione a non impostare un numero totale di passi troppo elevato. Per esempio, se si itera un gran numero di elementi, considerare l'impostazione di una frequenza maggiore, richiamando setredrawfrequency(), in modo da aggiornare solamente ogni tot iterazioni: Listing - 0 $progress->start($output, 0000); // aggiorna ogni 00 iterazioni $progress->setredrawfrequency(00); $i = 0; while ($i++ < 0000) fare qualcosa $progress->advance(); Segnaposto personalizzati Se si vogliono mostrare alcune informazioni che dipendono dalla barra di progressione e non sono diponibili tra i segnaposto predefiniti, se ne possono creare di propri. Vediamo come creare un segnaposto remaining_steps, che mostri il numero di passi rimanenti: Listing - ProgressBar::setPlaceholderFormatter( '%remaining_steps%', function (ProgressBar $bar, OutputInterface $output) return $bar->getmaxsteps() - $bar->getstep(); ); Messaggi personalizzati Il segnaposto %message% consente di specificare un messaggio perrsonalizzato, da mostrare insieme alla barra di progressione. Se però ne servono più di uno, basta definire il proprio: Listing - 0 $bar->setmessage('inizio'); $bar->setmessage('', 'filename'); $bar->start(); $bar->setmessage('in corso...'); while ($file = array_pop($files)) $bar->setmessage($filename, 'filename'); $bar->advance(); $bar->setmessage('finito'); $bar->setmessage('', 'filename'); $bar->finish(); Per la parte filename della barra di progressione, basta aggiungere il segnaposto %filename% al proprio formato: Listing -. generated on August, 0 Chapter : Barra di progressione
76 $bar->setformat(" %message%\n %step%/%max%\n Working on %filename%"); generated on August, 0 Chapter : Barra di progressione
77 Chapter Aiutante Progress New in version.: Il metodo setcurrent è stato introdotto in Symfony.. New in version.: Il metodo clear è stato introdotto in Symfony.. L'aiutante Progress è stato deprecato in Symfony. e sarà rimosso in Symfony.0. Si dovrebbe invece usare la barra di progressione, che è più potente. Quando si eseguono comandi che devono girare a lungo, può essere utile mostrare una barra di progressione, che si aggiorna durante l'esecuzione: Per mostrare dettagli sulla progressione, usare la classe ProgressHelper, passando il numero totale di unità e avanzando la progressione man mano che il comando gira: Listing - 0 $progress = $this->gethelperset()->get('progress'); $progress->start($output, 0); $i = 0; while ($i++ < 0) fare qualcosa // avanzare la barra di progressione di unità $progress->advance(); $progress->finish();. generated on August, 0 Chapter : Aiutante Progress
78 Si può anche impostare la progressione attaule, richiamando il metodo setcurrent(). Se si vuole mostrare qualcosa mentre la barra di progressione avanza, richiamare prima clear(). Dopo aver finito, richiamre display() per mostrare di nuovo la barra di progressione. Si può anche personalizzare il modo in cui la progressione viene mostrata, con vari livelli di verbosità. Ciascuno di questi mostra diversi possibili elementi, come la percentuale di completamento, una barra mobile, o informazioni su attuale/totale (p.e. 0/0): Listing - $progress->setformat(progresshelper::format_quiet); $progress->setformat(progresshelper::format_normal); $progress->setformat(progresshelper::format_verbose); $progress->setformat(progresshelper::format_quiet_nomax); // valore predefinito $progress->setformat(progresshelper::format_normal_nomax); $progress->setformat(progresshelper::format_verbose_nomax); Si possono anche controllare i vari caratteri e la larghezza usata per la barra: Listing - // la parte finale della barra $progress->setbarcharacter('<comment>=</comment>'); // la parte in corso della barra $progress->setemptybarcharacter(' '); $progress->setprogresscharacter(' '); $progress->setbarwidth(0); Per tutte le opzioni disponibili, consultare la documentazione delle API di ProgressHelper. Per questioni prestazionali, fare attenzione a non impostare un numero totale di passi troppo elevato. Per esempio, se si itera un gran numero di elementi, considerare l'impostazione di una frequenza maggiore, richiamando setredrawfrequency(), in modo da aggiornare solamente ogni tot iterazioni: Listing - 0 $progress->start($output, 0000); // avanzare ogni 00 iterazioni $progress->setredrawfrequency(00); $i = 0; while ($i++ < 0000) fare qualcosa $progress->advance(); generated on August, 0 Chapter : Aiutante Progress
79 Chapter Aiutante Question New in version.: L'aiutante Question è stato introdotto in Symfony.. QuestionHelper fornisce funzioni per chiedere all'utente maggiori informazioni. Fa parte degli aiutanti predefiniti, il cui elenco si può ottenere richiamando gethelperset() : Listing - $helper = $this->gethelper('question'); L'aiutante Question ha un solo metodo, ask(), che ha bisogno di un'istanza di InputInterface come primo parametro, una istanza di OutputInterface come secondo parametro e una Question come ultimo parametro. Chiedere conferma all'utente Si supponga di voler confermare un'azione, prima di esegurila. Aggiungere a un comando: Listing - use Symfony\Component\Console\Question\ConfirmationQuestion; $helper = $this->gethelper('question'); $question = new ConfirmationQuestion('Continuare con questa azione?', false); if (!$helper->ask($input, $output, $question)) return; In questo caso, all'utente sarà posta la domanda "Continuare con questa azione?". Se l'utente risponde con y, restituirà true, se invece risponderà n restituirà false. Il secondo parametro di construct() generated on August, 0 Chapter : Aiutante Question
80 è il valore predefinito da restituire se l'utente non risponde nulla. Qualsiasi risposta diversa farà sì che la domanda venga posta nuovamente. Chiedere informazioni all'utente Si possono anche porre domande con risposte più elaborate. Per esempio, se si vuole sapere il nome di un bundle, si può aggiungere a un comando: Listing - use Symfony\Component\Console\Question\Question; $question = new Question('Si prega di inserire il nome del bundle', 'AcmeDemoBundle'); $bundle = $helper->ask($input, $output, $question); All'utente sarà chiesto "Si prega di inserire il nome del bundle". L'utente può inserire un nome, che sarà restituito dal metodo ask(). Se la risposta sarà vuota, sarà restituito il valore predefinito (AcmeDemoBundle, in questo caso). Far scegliere da una lista di risposte Se si ha un insieme predefinito di risposte, tra cui l'utente può scegliere, si può usare ChoiceQuestion, che si assicura che l'utente inserisca una stringa valida, cioè compresa nella lista: Listing - 0 use Symfony\Component\Console\Question\ChoiceQuestion; $helper = $this->gethelper('question'); $question = new ChoiceQuestion( 'Si prega di scegliere un colore (predefinito: rosso)', array('rosso', 'blu', 'giallo'), 0 ); $question->seterrormessage('il colore %s non è valido.'); $color = $helper->ask($input, $output, $question); $output->writeln('colore scelto: '.$color); fare qualcosa con il colore L'opzione predefinita viene fornità dal terzo parametro del costruttore. Il valore predefinito è null, che significa che nessuna opzione sarà quella predefinita. Se l'utente inserisce una stringa non valida, viene mostrato un messaggio di errore e chiesta nuovamente la domanda, fino a che l'utente non inserirà una stringa valida o raggiungerà il numero massimo di tentativi. Il valore predefinito per il numero massimo di tentativi è null, che significa un numero infinito di tentativi. Si può definire un messaggio di errore, usando seterrormessage() construct generated on August, 0 Chapter : Aiutante Question 0
81 Scelte multiple A volte, si possono dare più risposte. ChoiceQuestion fornisce questa caratteristica, usando valori separati da virgole. Per abilitarla, usare setmultiselect() : Listing - 0 use Symfony\Component\Console\Question\ChoiceQuestion; $helper = $this->gethelper('question'); $question = new ChoiceQuestion( 'Si prega di scegliere dei colori (predefiniti: rosso e blu)', array('rosso', 'blu', 'giallo'), '0,' ); $question->setmultiselect(true); $colors = $helper->ask($input, $output, $question); $output->writeln('colori scelti: '. implode(', ', $colors)); Se l'utente inserisce,, il risultato sarà: Colori scelti: blu, giallo. Se l'utente non inserisce niente, il risultato sarà: Colori scelti: rosso, blu. Completamento Si può anche specificare un array di potenziali risposte per una data domanda. Questi forniranno un completamento automatico, mentre l'utente scrive: Listing - use Symfony\Component\Console\Question\Question; $bundles = array('acmedemobundle', 'AcmeBlogBundle', 'AcmeStoreBundle'); $question = new Question('Si prega di inserire il nome di un bundle', 'PippoBundle'); $question->setautocompletervalues($bundles); $name = $helper->ask($input, $output, $question); Nascondere la risposta Si può anche fare una domanda e nascondere la risposta. Questo è particolarmente utile per le password: Listing - use Symfony\Component\Console\Question\Question; $question = new Question('Inserire una password'); $question->sethidden(true); $question->sethiddenfallback(false); $password = $helper->ask($input, $output, $question);. generated on August, 0 Chapter : Aiutante Question
82 Quando si usa una risposta nascosta, Symfony userà un binario, cambierà modalità stty o userà un altro trucco per nascodere la risposta. Se nessuno di questi è disponibile, si arrenderà e consentirà alla risposta di essere visibile, a meno che non si imposti questo comportamento a false, usando sethiddenfallback(), come nell'esempio precedente. In questo caso, sarà sollevata una``runtimeexception``. Validare la risposta Si può anche validare la risposta. Per esempio, nell'ultimo esempio è stato chiesto il nome di un bundle. Seguendo le convenzioni di Symfony, il nome dovrebbe avere il suffisso Bundle. Lo si può validare, usando il metodo setvalidator() : Listing - 0 use Symfony\Component\Console\Question\Question; $question = new Question('Si prega di inserire il nome del bundle', 'AcmeDemoBundle'); $question->setvalidator(function ($answer) if ('Bundle'!== substr($answer, -)) throw new \RuntimeException( 'Il nome del bundle deve terminare con \'Bundle\'' ); return $answer; ); $question->setmaxattempts(); $name = $helper->ask($input, $output, $question); Il parametro $validator è un callback, che gestisce la validazione. Dovrebbe lanciare un'eccezione se qualcosa va storto. Il messaggio dell'eccezione è mostrato nella console, quindi è una buona pratica inserirvi delle informazioni rilevanti. IL callback dovrebbe anche restituire il valore inserito dall'utente, in caso di successo. Si può impostare il numero massimo di volte in cui fare la domanda, con il metodo setmaxattempts(). Una volta raggiunto tale numero, sarà usato il valore predefinito. Usando null si indica che il numero di tentativi è infinito. L'utente vedrà la domanda finché inserisce una risposta non valida e potrà procedere solo in caso di risposta valida. Validare una risposta nascosta Si può anche usare un validatore con una risposta nascosta: Listing - use Symfony\Component\Console\Question\Question; $helper = $this->gethelper('question'); $question = new Question('Inserire la password'); $question->setvalidator(function ($value) if (trim($value) == '') generated on August, 0 Chapter : Aiutante Question
83 0 throw new \Exception('La password non può essere vuota'); return $value; ); $question->sethidden(true); $question->setmaxattempts(0); $password = $helper->ask($input, $output, $question); Testare un comando con un input atteso Se si vuole scrivere un test per un comando che si aspetta un qualche tipo di input da linea di omando, occorre sovrascrivere HelperSet usato dal comando: Listing use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Tester\CommandTester; public function testexecute() $commandtester = new CommandTester($command); $helper = $command->gethelper('question'); $helper->setinputstream($this->getinputstream('test\\n')); // Equivale all'utente che inserisce "Test" e preme invio // Se serve una conferma, va bene "yes\n" $commandtester->execute(array('command' => $command->getname())); // $this->assertregexp('/.../', $commandtester->getdisplay()); protected function getinputstream($input) $stream = fopen('php://memory', 'r+', false); fputs($stream, $input); rewind($stream); return $stream; Impostando il flusso di input di QuestionHelper, si imita ciò che la console farebbe internamente con l'input dell'utente tramite cli. In questo modo, si può testare ogni interazione, anche complessa, passando un appropriato flusso di input. generated on August, 0 Chapter : Aiutante Question
84 Chapter Tabella New in version.: La classe Table è stata introdotta in Symfony. come rimpiazzo per l'aiutante Table. Può essere utile mostrare dati in tabella in un'applicazione di console: Listing ISBN Titolo Autore La divina commedia Dante Alighieri A Tale of Two Cities Charles Dickens The Lord of the Rings J. R. R. Tolkien And Then There Were None Agatha Christie Per mostrare una tabella, usare Table, impostare i titoli, impostare le righe e quindi rendere la tabella: Listing - 0 use Symfony\Component\Console\Helper\Table; $table = new Table($output); $table ->setheaders(array('isbn', 'Titolo', 'Autore')) ->setrows(array( array('--0-', 'La divina commedia', 'Dante Alighieri'), array('--00-0', 'A Tale of Two Cities', 'Charles Dickens'), array('0--0-0', 'The Lord of the Rings', 'J. R. R. Tolkien'), array('0-0--', 'And Then There Were None', 'Agatha Christie'), )) ; $table->render(); Si può aggiumgere un separatore in un punto dell'output, passando un'istanza di TableSeparator come riga: Listing generated on August, 0 Chapter : Tabella
85 use Symfony\Component\Console\Helper\TableSeparator; $table->setrows(array( array('--0-', 'La divina commedia', 'Dante Alighieri'), array('--00-0', 'A Tale of Two Cities', 'Charles Dickens'), new TableSeparator(), array('0--0-0', 'The Lord of the Rings', 'J. R. R. Tolkien'), array('0-0--', 'And Then There Were None', 'Agatha Christie'), )); Listing ISBN Titolo Autore La divina commedia Dante Alighieri A Tale of Two Cities Charles Dickens The Lord of the Rings J. R. R. Tolkien And Then There Were None Agatha Christie Si può cambiare stile, usando uno di quelli disponibili, tramite setstyle() : Listing - // lo stesso che non impostare niente $table->setstyle('default'); // cambia in stile compatto $table->setstyle('compact'); $table->render(); Il risultato è: Listing - ISBN Titolo Autore --0- La divina commedia Dante Alighieri A Tale of Two Cities Charles Dickens The Lord of the Rings J. R. R. Tolkien And Then There Were None Agatha Christie Si può anche impostare lo stile a borderless: Listing - $table->setstyle('borderless'); $table->render(); che mostra: Listing - =============== ========================== ================== ISBN Titolo Autore =============== ========================== ================== --0- La divina commedia Dante Alighieri A Tale of Two Cities Charles Dickens The Lord of the Rings J. R. R. Tolkien And Then There Were None Agatha Christie =============== ========================== ==================. generated on August, 0 Chapter : Tabella
86 Se nessuno degli stili predefiniti è adatto, se ne può definire uno nuovo: Listing - 0 use Symfony\Component\Console\Helper\TableStyle; // basato su "default", se non specificato diversamente $style = new TableStyle(); // personalizzare lo stile $style ->sethorizontalborderchar('<fg=magenta> </>') ->setverticalborderchar('<fg=magenta>-</>') ->setcrossingchar(' ') ; // usare lo stile $table->setstyle($style); Ecco una lista delle cose che si possono personalizzare: setpaddingchar() sethorizontalborderchar() setverticalborderchar() setcrossingchar() setcellheaderformat() setcellrowformat() setborderformat() 0 setpadtype() Si può anche registrare uno stile globalmente: Listing -0 // registra lo stile con nome "colorato" Table::setStyleDefinition('colorato', $style); // usa lo stile $table->setstyle('colorato'); Si può usare lo steso metodo per ridefinire uno degli stili predefiniti generated on August, 0 Chapter : Tabella
87 Chapter Aiutante Table New in version.: L'aiutante table è stato introdotto in Symfony.. L'aiutante Table è stato deprecato in Symfony. e sarà rimosso in Symfony.0. Si dovrebbe usare ora la classe Table, che è più potente. Quando si costruisce un'applicazione in console, può essere utile mostrare dati tabulari: Per mostrare una tabella, usare TableHelper, impostando testata e righe, e rendere: Listing - 0 $table = $this->gethelper('table'); $table ->setheaders(array('isbn', 'Titolo', 'Autore')) ->setrows(array( array('--0-', 'La divina commedia', 'Dante Alighieri'), array('--00-0', 'Racconto di due città', 'Charles Dickens'), array('0--0-0', 'Il signore degli anelli', 'J. R. R. Tolkien'), array('0-0--', 'Dieci piccoli indiani', 'Agatha Christie'), )) ; $table->render($output); Si può personalizzare anche il formato della tabella. Ci sono due modi per personalizzare la resa della tabella: con nomi di formato o personalizzando le opzioni di resa.. generated on August, 0 Chapter : Aiutante Table
88 Personalizzare il formato della tabella con i nomi di formati New in version.: Il formato TableHelper::LAYOUT_COMPACT è stato introdotto in Symfony.. L'aiutante Table dispone di due formati di tabella preconfigurati: TableHelper::LAYOUT_DEFAULT TableHelper::LAYOUT_BORDERLESS TableHelper::LAYOUT_COMPACT Si può impostare il formato con il metodo setlayout(). Personalizzare il formato della tabella con le opzioni di resa Si può anche controllare la resa della tabella impostando i valori delle opzioni di resa: setpaddingchar() sethorizontalborderchar() setverticalborderchar() setcrossingchar() setcellheaderformat() setcellrowformat() setborderformat() setpadtype() generated on August, 0 Chapter : Aiutante Table
89 generated on August, 0 Chapter : Aiutante Table
90 Chapter Il componente CssSelector Il componente CssSelector converte selettori CSS in espressioni XPath. Installazione Si può installare il componente in due modi: Installarlo via Composer (symfony/css-selector su Packagist ); Usare il repository ufficiale su Git ( ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Uso Perché usare selettori CSS? Quando si analizzano documenti HTML o XML, XPath è certamente il metodo più potente. Le espressioni XPath sono incredibilmente flessibili, quindi c'è quasi sempre un'espressione XPath che troverò l'elemento richiesto. Sfortunatamente, possono essere anche molto complicate e la curva di apprendimento è ripida. Anche operazioni comuni (come trovare un elemento con una data classe) possono richiedere espressioni lunghe e poco maneggevoli. Molti sviluppatori, in particolare gli sviluppatori web, si trovano più a loro agio nel cercare elementi tramite selettori CSS. Oltre a funzionare con i fogli di stile, i selettori CSS sono usati da Javascript con la funzione queryselectorall e da famose librerie Javascript, come jquery, Prototype e MooTools generated on August, 0 Chapter : Il componente CssSelector 0
91 I selettori CSS sono meno potenti di XPath, ma molto più facili da scrivere, leggere e capire. Essendo meno potenti, quasi tutti i selettori CSS possono essere convertiti in equivalenti XPath. Queste espressioni XPath possono quindi essere usate con altre funzioni e classi che usano XPath per trovare elementi in un documento. Il componente CssSelector L'unico fine del componente è la conversione di selettori CSS nei loro equivalenti XPath: Listing - use Symfony\Component\CssSelector\CssSelector; print CssSelector::toXPath('div.item > h > a'); Questo fornisce il seguente output: Listing - descendant-or-self::div[contains(concat(' ',normalize-space(@class), ' '), ' item ')]/h/a Si può usare questa espressione, per esempio, con DOMXPath o SimpleXMLElement, per trovare elementi in un documento. Il metodo Crawler::filter() usa il componente CssSelector per trovare elementi in base a un selettore CSS. Si veda Il componente DomCrawler per ulteriori dettagli. Limiti del componente CssSelector Non tutti i selettori CSS possono essere convertiti in equivalenti XPath. Ci sono molti selettori CSS che hanno senso solo nel contesto di un browser. selettori dei collegamenti: :link, :visited, :target selettori basati su azioni dell'utente: :hover, :focus, :active selettori dello stato dell'interfaccia: :enabled, :disabled, :indeterminate (tuttavia, :checked e :unchecked sono disponibili) Gli pseudo-elementi (:before, :after, :first-line, :first-letter) non sono supportati, perché selezionano porzioni di testo, piuttosto che elementi. Diverse pseudo-classi non sono ancora supportate: *:first-of-type, *:last-of-type, *:nth-of-type, *:nth-last-of-type, *:only-oftype. (funzionano con il nome di un elemento (p.e. li:first-of-type) ma non con * generated on August, 0 Chapter : Il componente CssSelector
92 Chapter Il componente Debug Il componente Debug fornisce strumenti per facilitare il debug del codice PHP New in version.: Il componente Debug è nuovo in Symfony.. In precedenza, le classi si trovavano nel componente HttpKernel. Installazione Si può installare il componente in due modi: Usando il repository ufficiale su Git ( ); Installandolo tramite Composer (symfony/debug su Packagist ). Uso Il componente Debug fornisce diversi strumenti che aiutano nel debug del codice PHP. Per abilitarlo, bastano poche istruzioni: Listing - use Symfony\Component\Debug\Debug; Debug::enable(); Il metodo enable() registra un gestore di errori e un gestore di eccezioni e uno speciale caricatore di classi. Leggere le sezioni seguenti per maggiori informazioni sui vari strumenti a disposizione generated on August, 0 Chapter : Il componente Debug
93 Non si dovrebbero mai abilitare gli strumenti di debug in ambiente di produzione, perché potrebbero rivelare informazioni sensibili agli utenti. Abilitare il gestore di errori La classe ErrorHandler cattura gli errori PHP e li converte in eccezioni (di classe ErrorException o FatalErrorException per errori fatali PHP): Listing - use Symfony\Component\Debug\ErrorHandler; ErrorHandler::register(); Abilitare il gestore di eccezioni La classe ExceptionHandler cattura le eccezioni PHP non catturate e le converte in una risposta PHP. È utile in modalità di debug, per sostituire l'output predefinito di XDebug con qualcosa di più elegante e utile: Listing - use Symfony\Component\Debug\ExceptionHandler; ExceptionHandler::register(); Se è disponibile il componente HttpFoundation, il gestore usa un oggetto Response di Symfony. Altrimenti, si accontenta di una semplice risposta PHP generated on August, 0 Chapter : Il componente Debug
94 Chapter Debug di ClassLoader DebugClassLoader tenta di lanciare eccezioni più utili, quando gli autoloader registrati non trovano una classe. Tutti gli autoloader che implementano un metodo findfile() vengono sostituiti da un DebugClassLoader. L'uso di DebugClassLoader è facile, basta richiamare il metodo statico enable() : Listing - use Symfony\Component\Debug\DebugClassLoader; DebugClassLoader::enable();. generated on August, 0 Chapter : Debug di ClassLoader
95 Chapter 0 Il componente DependencyInjection Il componente DependencyInjection consente di standardizzare e centralizzare il modo in cui gli oggetti sono costruiti nelle applicazioni. Per un'introduzione alla dependency injection e ai contenitori di servizi, vedere Contenitore di servizi Installazione È possibile installare il componente in due modi: Installandolo via Composer (symfony/dependency-injection su Packagist ); Utilizzando il repository ufficiale su Git ( ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Utilizzo Si potrebbe avere una semplice classe, come la seguente Mailer, che si vuole rendere disponibile come servizio: Listing 0- class Mailer private $transport; public function construct(). generated on August, 0 Chapter 0: Il componente DependencyInjection
96 0 $this->transport = 'sendmail'; La si può registrare nel contenitore come servizio: Listing 0- use Symfony\Component\DependencyInjection\ContainerBuilder; $container = new ContainerBuilder(); $container->register('mailer', 'Mailer'); Si potrebbe migliorare la classe, per renderla più flessibile, consentendo al contenitore di impostare il trasporto usato. Si può cambiare la classe, in modo che il trasporto sia passato al costruttore: Listing 0-0 class Mailer private $transport; public function construct($transport) $this->transport = $transport; Si può quindi impostare la scelta del trasporto nel contenitore: Listing 0- use Symfony\Component\DependencyInjection\ContainerBuilder; $container = new ContainerBuilder(); $container ->register('mailer', 'Mailer') ->addargument('sendmail'); La classe ora è molto più flessibile, perché la scelta del trasporto è stata separata dall'implementazione e posta nel contenitore. La scelta del trasporto potrebbe interessare anche altri servizi. Si può evitare di doverlo cambiare in posti differenti, trasformandolo in un parametro nel contenitore e facendo riferimento a tale parametro per il costruttore del servizio Mailer: Listing 0- use Symfony\Component\DependencyInjection\ContainerBuilder; $container = new ContainerBuilder(); $container->setparameter('mailer.transport', 'sendmail'); $container ->register('mailer', 'Mailer') ->addargument('%mailer.transport%'); Ora che il servizio mailer è nel contenitore, lo si può iniettare come dipendenza di altre classi. Se si ha una classe NewsletterManager come questa: Listing 0- generated on August, 0 Chapter 0: Il componente DependencyInjection
97 0 class NewsletterManager private $mailer; public function construct(\mailer $mailer) $this->mailer = $mailer; Allora la si può registrare come servizio e passarle il servizio mailer: Listing 0-0 use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; $container = new ContainerBuilder(); $container->setparameter('mailer.transport', 'sendmail'); $container ->register('mailer', 'Mailer') ->addargument('%mailer.transport%'); $container ->register('newsletter_manager', 'NewsletterManager') ->addargument(new Reference('mailer')); Se NewsletterManager non richiedesse Mailer e l'iniezione fosse quindi solamente opzionale, la si potrebbe passare usando un setter: Listing 0-0 class NewsletterManager private $mailer; public function setmailer(\mailer $mailer) $this->mailer = $mailer; Ora si può scegliere di non iniettare un Mailer dentro NewsletterManager. Se comunque lo si volesse fare, il contenitore può richiamare il metodo setter: Listing 0-0 use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; $container = new ContainerBuilder(); $container->setparameter('mailer.transport', 'sendmail'); $container ->register('mailer', 'Mailer') ->addargument('%mailer.transport%'); generated on August, 0 Chapter 0: Il componente DependencyInjection
98 $container ->register('newsletter_manager', 'NewsletterManager') ->addmethodcall('setmailer', new Reference('mailer')); Si può quindi ottenere il servizio newsletter_manager dal contenitore, in questo modo: Listing 0-0 use Symfony\Component\DependencyInjection\ContainerBuilder; $container = new ContainerBuilder(); $newslettermanager = $container->get('newsletter_manager'); Evitare che il codice dipenda dal contenitore Sebbene si possano recuperare servizi direttamente dal contenitore, sarebbe meglio minimizzarlo. Per esempio, in NewsletterManager abbiamo iniettato il servizio mailer, piuttosto che richiederlo al contenitore. Avremo potuto iniettare il contenitore e recuperare da esso il servizio mailer, ma allora sarebbe stato legato a questo particolare contenitore, rendendo difficile riusare la classe altrove. A un certo punto si avrà la necessità di ottenere un servizio dal contenitore, ma lo si dovrebbe fare il meno possibile e all'inizio dell'applicazione. Impostare il contenitore con file di configurazione Oltre a impostare servizi usando PHP, come sopra, si possono usare dei file di configurazione. Ciò consente di usare XML o Yaml per scrivere definizioni per i servizi, invece di usare PHP per definire i servizi, come visto negli esempi precedenti. In applicazioni che non siano piccole ha senso organizzare le definizioni dei servizi, spostandole in più file di configurazione. Per poterlo fare, occorre installare anche il componente Config: Caricare un file di configurazione xml: Listing 0- use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; $container = new ContainerBuilder(); $loader = new XmlFileLoader($container, new FileLocator( DIR )); $loader->load('services.xml'); Caricare un file di configurazione yaml: Listing 0- use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; $container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator( DIR )); $loader->load('services.yml'); generated on August, 0 Chapter 0: Il componente DependencyInjection
99 Se si vogliono caricare file di configurazione YAML, occorrerà installare anche il componente YAML. Se si vuole usare PHP per creare i servizi, si possono spostare in un file di configurazione separato e caricarlo in modo simile: Listing 0- use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; $container = new ContainerBuilder(); $loader = new PhpFileLoader($container, new FileLocator( DIR )); $loader->load('services.php'); I servizi newsletter_manager e mailer possono ora essere impostati da file di configurazione: Listing 0-0 parameters: #... mailer.transport: sendmail services: mailer: class: Mailer arguments: ["%mailer.transport%"] newsletter_manager: class: NewsletterManager calls: - [setmailer, ["@mailer"]] generated on August, 0 Chapter 0: Il componente DependencyInjection
100 Chapter Tipi di iniezione Esplicitare le dipendenze di una classe e richiedere che vi siano iniettate è un buono modo per rendere una classe più usabile, testabile e disaccoppiata da altre. Ci sono molti modi per iniettare le dipendenze. Ogni tipo di iniezione ha vantaggi e svantaggi da considerare, così come diversi modi di funzionare nell'uso con il contenitore di servizi. Iniezione nel costruttore Il modo più comune per iniettare dipendenze è tramite il costruttore di una classe. Per poterlo fare, occorre aggiungere un parametro alla firma del costruttore, in modo che accetti la dipendenza: Listing - 0 class NewsletterManager protected $mailer; public function construct(\mailer $mailer) $this->mailer = $mailer; Si può specificare quale servizio si vuole iniettare, tramite la configurazione del contenitore di servizi: Listing - services: mio_mailer: #... newsletter_manager: class: NewsletterManager arguments: ["@mio_mailer"] generated on August, 0 Chapter : Tipi di iniezione 00
101 Forzare il tipo di un oggetto iniettato implica la certezza che venga iniettata una dipendenza effettivamente utilizzabile. Forzando il tipo, si otterrà subito un errore chiaro, nel caso in cui venga iniettata una dipendenza inadatta. Forzando il tipo con un'interfaccia invece che con una classe, si può rendere più flessibile la scelta della dipendenza. Ipotizzando di usare solo i metodi definiti nell'interfaccia, si può guadagnare flessibilità e mantenere la sicurezza. Ci sono diversi vantaggi nell'uso dell'iniezione nel costruttore: Se la dipendenza è un requisito e la classe non funziona senza di essa, l'iniezione tramite il costruttore assicura che sia presente all'uso della classe, poiché la classe non può essere istanziata senza. Il costruttore è richiamato solo una volta, alla creazione dell'oggetto, quindi si può essere sicuri che la dipendenza non cambierà durante il ciclo di vita dell'oggetto. Tali vantaggi implicano che l'iniezione nel costruttore non è adatta per le dipendenze facoltative. Inoltre è più difficile da usare in combinazione con le gerarchie di classi: se una classe usa l'iniezione nel costruttore, estenderla e sovrascrivere il costruttore diventa problematico. Iniezione da setter Un altro possibile punto di iniezione in una classe è l'aggiunta di un metodo setter, che accetti la dipendenza: Listing - 0 class NewsletterManager protected $mailer; public function setmailer(\mailer $mailer) $this->mailer = $mailer; Listing - services: mio_mailer: #... newsletter_manager: class: NewsletterManager calls: - [setmailer, ["@mio_mailer"]] Questa volta i vantaggi sono: l'iniezione da setter funziona bene con le dipendenza facoltative. Se non si ha bisogno della dipendenza, basta non richiamare il setter. Si può richiamare il setter più volte. Questo è particolarmente utile se il metodo aggiunge la dipendenza a un insieme. Si può quindi avere un numero variabile di dipendenze. Gli svantaggi dell'iniezione da setter sono: Il setter può essere richiamato più volte, non solo all'istanza dell'oggetto, quindi non si può essere sicuri che la dipendenza non sia rimpiazzata durante il ciclo di vita dell'oggetto (tranne se si scrive esplicitamente il metodo setter per verificare se non sia stato già richiamato). generated on August, 0 Chapter : Tipi di iniezione 0
102 Non si può essere sicuri che il setter sia richiamato, quindi occorre verificare che ogni dipendenza obbligatoria sia iniettata. Iniezione di proprietà Un'altra possibilità consiste nell'impostare direttamente campi pubblici della classe: Listing - class NewsletterManager public $mailer; Listing - services: mio_mailer: #... newsletter_manager: class: NewsletterManager properties: mailer: "@mio_mailer" Ci sono principalmente solo svantaggi nell'uso dell'iniezione di proprietà, che è simile a quella da setter, ma con importanti problemi ulteriori: Non si può in alcun modo controllare quando la dipendenza viene impostata, potrebbe essere modificata in qualsiasi punto del ciclo di vita dell'oggetto. Non si può forzare il tipo, quindi non si può essere sicuri di quale dipendenza sia iniettata, a meno di non scrivere nella classe esplicitamente di testare l'istanza della classe prima del suo uso. Tuttavia, è utile conoscere questa possibilità del contenitore di servizi, specialmente se si lavora con codice fuori dal proprio controllo, come in librerie di terze parti, che usino proprietà pubbliche per le proprie dipendenze. generated on August, 0 Chapter : Tipi di iniezione 0
103 Chapter Introduzione ai parametri Si possono definire, nel contenitore di servizi, parametri che possono essere usati direttamente o come parte di definizioni di servizi. Questo è di aiuto per separare valori che si vorranno cambiare più spesso. Recuperare e impostare parametri del contenitore È facile lavorare coi parametri del contenitore, usando i metodi di accesso del contenitore per i parametri. Si può verificare se un parametro sia stato definito nel contenitore con: Listing - $container->hasparameter('mailer.transport'); Si può recuperare un parametro impostato nel contenitore con: Listing - $container->getparameter('mailer.transport'); e impostare un parametro nel contenitore con: Listing - $container->setparameter('mailer.transport', 'sendmail'); La notazione. usata è solo una convenzione di Symfony per rendere più leggibili i parametri. I parametri sono solo elementi chiave-valore e non possono essere organizzati in array. Si può impostare un parametro solo prima che il contenitore sia compilato. Per saperne di più sulla compilazione del contenitore, vedere Compilazione del contenitore. generated on August, 0 Chapter : Introduzione ai parametri 0
104 Parametri nei file di configurazione Si può anche usare la sezione parameters di un file di configurazione per impostare parametri: Listing - parameters: mailer.transport: sendmail Come per il recupero diretto dei valori dei parametri dal contenitore, si può anche usarli nei file di configurazione. Si può fare riferimento ai parametri da altrove, inserendoli tra simboli di percentuale (%), p.e. %mailer.transport%. Un possibile uso è iniettare i valori nei servizi. Questo consente di configurare varie versioni dei servizi tra le applicazioni o vari servizi basati sulle stesse classi ma configurati diversamente, in una singola applicazione. Si può iniettare la scelta del trasporto dell' nella classe Mailer direttamente, ma rendendola un parametro. Questo rende più facile cambiarla, rispetto ad averla legata e nascosta nella definizione del servizio: Listing - parameters: mailer.transport: sendmail services: mailer: class: Mailer arguments: ['%mailer.transport%'] Gli spazi nei valori tra i tag parameter nei file di configurazione XML non sono eliminati. Questo vuol dire che il seguente pezzo di configurazione avrà come valore \n sendmail\n: Listing - <parameter key="mailer.transport"> sendmail </parameter> In alcuni casi (per costanti o nomi di classi), ciò potrebbe causare errori. Per evitarlo, usare sempre una singola riga per i parametri, come segue: Listing - <parameter key="mailer.transport">sendmail</parameter> In caso di uso altrove, occorre cambiare il parametro in un unico posto, se necessario. Si può anche usare i parametri nella definizione dei servizi, per esempio, rendendo un parametro la classe di un servizio: Listing - parameters: mailer.transport: sendmail mailer.class: Mailer services: mailer: class: "%mailer.class%" arguments: ["%mailer.transport%"] generated on August, 0 Chapter : Introduzione ai parametri 0
105 Il simbolo di percentuale dentro a un parametro o argomento, come parte della stringa, deve subire un escape con un ulteriore simbolo di percentuale: Listing - arguments: [" Parametri array I parametri non devono necessariamente essere semplici stringhe, possono anche essere array. Per il formato XML, occorre usare l'attributo type="collection" per tutti i parametri che sono array. Listing -0 0 parameters: my_mailer.gateways: - mail - mail - mail my_multilang.language_fallback: en: - en - fr fr: - fr - en Costanti come parametri Il contenitore supporta anche l'impostazione di costanti PHP come parametri. Per sfruttare questa caratteristica, mappare il nome della costante a un parametro e definire il tipo come constant. Listing - 0 <?xml version=".0" encoding="utf-"?> <container xmlns=" xmlns:xsi=" xsi:schemalocation=" dic/services/services-.0.xsd"> <parameters> <parameter key="global.constant.value" type="constant">costante_globale</parameter> <parameter key="my_class.constant.value" type="constant">mia_classe::nome_costante</parameter> </parameters> </container> Questo non funziona per configurazioni YAML. Se si usa YAML, si può importare un file XML per sfruttare tale funzionalità: Listing - imports: - resource: parameters.xml generated on August, 0 Chapter : Introduzione ai parametri 0
106 Parole chiave di PHP in XML Per impostazione predefinita, true, false e null in XML sono convertiti in parole chiave di PHP (rispettivamente true, false e null): Listing - <parameters> <parameter key="mailer.send_all_in_once">false</parameter> </parameters> <!-- dopo l'analisi $container->getparameter('mailer.send_all_in_once'); // restituisce false --> Per evitare questo comportamento, usare il tipo string: Listing - <parameters> <parameter key="mailer.some_parameter" type="string">true</parameter> </parameters> <!-- dopo l'analisi $container->getparameter('mailer.some_parameter'); // restituisce "true" --> Non disponibile per Yaml e PHP, che hanno già un supporto nativo per le parole chiave di PHP. Sintassi nel riferimento a servizi Si può fare riferimento a servizi, in modo leggermente diverso nei vari formati. Si può configurare il comportamento in caso di non esistenza del servizio a cui si fa riferimento. Il comportamento predefinito è lanciare un eccezione. YAML Aggiungere a inizio stringa per fare riferimento a un servizio in fa riferimento al servizio mailer. Se il servizio non esiste, lancia fa riferimento al servizio mailer. Se il servizio non esiste, sarà ignorato; Listing - parameters: # se il servizio 'my_mailer' non è definito, sarà sollevata un'eccezione # se il servizio 'my_logger' non è definito, 'bar' sarà nullo per l'escape del in sarà convertito nella stringa "@mailer" invece di fare riferimento al servizio mailer. generated on August, 0 Chapter : Introduzione ai parametri 0
107 XML In XML, usare il tipo service. Il comportamento in caso di servizio non esistente può essere definito usando il parametro on-invalid. Il comportamento predefinito è lanciare un'eccezione. Valori accettabili per on-invalid sono null (usa null al posto del servizio mancante) o ignored (molto simile, tranne che, se usato su una chiamata a metodo, la chiamata viene rimossa). Listing - <parameters> <!-- se il servizio 'my_mailer' non è definito, sarà sollevata un'eccezione --> <parameter key="foo" type="service" id="my_mailer" /> <!-- se il servizio 'my_logger' non è definito, 'bar' sarà nullo --> <parameter key="bar" type="service" id="my_logger" on-invalid="null" /> </parameters> PHP In PHP, si può usare la classe Reference per fare riferimento a un servizio. Il comportamento invalido si configura usando il secondo parametro del costruttore e le costenati di ContainerInterface. Listing - use Symfony\Component\DependencyInjection\Reference; // se il servizio 'my_mailer' non è definito, sarà sollevata un'eccezione $container->setparameter('foo', new Reference('my_mailer')); // se il servizio 'my_logger' non è definito, 'bar' sarà nullo $container->setparameter('bar', new Reference('my_logger', ContainerInterface::NULL_ON_INVALID_REFERENCE ));. generated on August, 0 Chapter : Introduzione ai parametri 0
108 Chapter Lavorare con parametri e definizioni del contenitore Ottenere e impostare parametri del contenitore Ci sono alcuni metodi utili per lavorare con le definizioni dei servizi. Per trovare se ci sia una definizione per un id di un servizio: Listing - $container->hasdefinition($idservizio); Questo è utile se si vuole far qualcosa solo se una particolare definizione esiste. Si possono recuperare una definizione con: Listing - $container->getdefinition($idservizio); oppure: Listing - $container->finddefinition($idservizio); che, diversamente da getdefinition(), risolve anche gli alias, in modo che, se $idservizio è un alias, si otterrà la definizione sottostante. Le definizioni stesse dei servizi sono oggetti, quindi se si recupera una definizione con tali metodi e li si modifica, tali modifiche si rifletteranno nel contenitore. Se, tuttavia, si crea una nuova definizione, la si può aggiungere al contenitore, usando: Listing - $container->setdefinition($id, $definizione); generated on August, 0 Chapter : Lavorare con parametri e definizioni del contenitore 0
109 Lavorare con una definizione Creare una nuova definizione Se occorre creare una nuova definizione, piuttosto che manipolarne una recuperata dal contenitore, alla la classe della definizione è Definition. Classe Quella vista sopra è la classe di una definizione, questa è la classe dell'oggetto restituito quando si richiede un servizio dal contenitore. Per trovare quale classe sia impostata per una definizione: Listing - $definition->getclass(); e per impostare una classe diversa: Listing - $definition->setclass($classe); // Il nome pienamente qualificato di una classe, come stringa Parametri del costruttore Per ottenere un array di parametri del costruttore per una definizione, si può usare: Listing - $definition->getarguments(); oppure si può ottenere un singolo parametro, in base alla sua posizione: Listing - $definition->getargument($indice); // p.e. $definition->getarguments(0) per il primo parametro Si può aggiungere un nuovo parametro alla fine dell'array dei parametri, usando: Listing - $definition->addargument($parametro); Il parametro può essere una stringa, un array, il parametro di un servizio, se si usa %paramater_name%, oppure l'id di un servizio, se si usa: Listing -0 use Symfony\Component\DependencyInjection\Reference; $definition->addargument(new Reference('id_servizio')); In modo simile, si può sostituire un parametro già impostato, usando: Listing - $definition->replaceargument($indice, $parametro); Si possono anche sostituire tutti i parametri (o impostarne alcuni, se non ce ne sono) con un array di parametri:. generated on August, 0 Chapter : Lavorare con parametri e definizioni del contenitore 0
110 Listing - $definition->replacearguments($parametri); Chiamate a metodi Se il servizio con cui si sta lavorando usa l'iniezione per setter, si può manipolare qualsiasi chiamata a metodi nelle definizioni. Si può ottenere un array di tutte le chiamate a metodi, con: Listing - $definition->getmethodcalls(); E la chiamata a un metodo, con: Listing - $definition->addmethodcall($metodo, $parametri); Dove $metodo è il nome del metodo e $parametri è un array dei parametri con cui richiamare il metodo. I parametri possono essere stringhe, array, parametri o id di servizi, come per i parametri del costruttore. Si possono anche sostituire le chiamate a metodi esistenti con un array di nuove, con: Listing - $definition->setmethodcalls($chiamate); Ci sono ulteriori esempi di modi specifici di lavorare con le definizioni nei blocchi di codice PHP degli esempi di configurazione nelle pagine come Usare un factory per creare servizi e Come gestire le dipendenze comuni con servizi genitori. I metodi visti qui che cambiano definizione dei servizi possono essere usati solo prima che il contenitore sia compilato: una volta che il contenitore è compilato, non si possono manipolare ultetiormente le definizioni dei servizi. Per saperne di più sulla compilazione del contenitore, vedere Compilazione del contenitore. generated on August, 0 Chapter : Lavorare con parametri e definizioni del contenitore 0
111 Chapter Compilazione del contenitore Ci sono diverse ragioni per compilare il contenitore di servizi. Tra queste, poter verificare potenziali problemi, come i riferimenti circolari, e rendere il contenitore più efficiente, risolvendo i parametri e rimuovendo i servizi inutilizzati. Inoltre, alcune caratteristiche, come l'uso di servizi genitori, necessitano di un contenitore compilato. La compilazione avviene eseguendo: Listing - $container->compile(); Il metodo compile usa dei passi di compilatore per la compilazione. Il componente DependencyInjection dispone di diversi passi, registrati automaticamente per la compilazione. Per esempio, CheckDefinitionValidityPass verifica diversi problemi potenziali con le definizioni impostate nel contenitore. Dopo questo e molti altri passi, che verificano la validità del contenitore, ulteriori passi sono usati per ottimizzare la configurazione, prima che sia messa in cache. Per esempio, vengono rimossi i servizi privati e i servizi astratti, e vengono risolti gli alias. Gestire la configurazione con le estensioni Oltre a caricare la configurazione direttamente nel contenitore, come mostrato in Il componente DependencyInjection, la si può gestire registrando estensioni con il contenitore. Il primo passo nel processo di compilazione consiste nel caricare la configurazione da un classe estensione, registrata con il contenitore. Diversamente dal caricamento diretto della configurazione, sono processate solo quando il contenitore viene compilato. Se l'applicazione è modulare, le estensioni consentono a ciascun modulo di registrare e gestire la propria configurazione dei servizi. Le estensioni devono implementare ExtensionInterface e possono essere registrare con il contenitore in questo modo: Listing - $container->registerextension($extension);. generated on August, 0 Chapter : Compilazione del contenitore
112 Il lavoro principale dell'estensione viene eseguito nel metodo load. In questo metodo si possono caricare configurazioni da uno o più file, oltre che manipolare le definizioni del contenitore, usando i metodi mostrati in Lavorare con parametri e definizioni del contenitore. Al metodo load viene passato un nuovo contenitore da configurare, il quale viene poi fuso nel contenitore con cui è registrato. Questo consente di avere diverse estensioni, che gestiscono le definizioni in modo indipendente. Quando vengono aggiunte, le estensioni non aggiungono configurazioni al contenitore, ma sono processato quando viene richiamato il metodo compile del contenitore. Un'estensione molto semplice potrebbe solo caricare file di configurazione nel contenitore: Listing - 0 use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\Config\FileLocator; class AcmeDemoExtension implements ExtensionInterface public function load(array $configs, ContainerBuilder $container) $loader = new XmlFileLoader( $container, new FileLocator( DIR.'/../Resources/config') ); $loader->load('services.xml'); Questo non fornisce molti vantaggi, rispetto a caricare il file direttamente nel contenitore generale in via di costruzione. Consente solo ai file di essere suddivisi tra i moduli/bundle. La possibilità di influenzare la configurazione di un modulo dai file di configurazione esterni al modulo/bundle è necessaria per rendere configurabile un'applicazione complessa. Lo si può fare specificando che delle sezioni dei file di configurazione caricati direttamente nel contenitore appartengono a una particolare estensione. Tali sezioni non saranno processate direttamente dal contenitore, ma dall'estensione relativa. L'estensione deve specificare un metodo getalias, per implementare l'interfaccia: Listing - 0 class AcmeDemoExtension implements ExtensionInterface public function getalias() return 'acme_demo'; Per i file di configurazione YAML, specificare l'alias per l'estensione come chiave vorrà dire che tali valori sono passati al metodo load dell'estensione: Listing - #... acme_demo: pippo: valoredipippo pluto: valoredipluto generated on August, 0 Chapter : Compilazione del contenitore
113 Se questo file viene caricato nella configurazione, i valori in esso sono processati solo quando il contenitore viene compilato nel punto in cui viene caricata l'estensione: Listing - 0 use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; $container = new ContainerBuilder(); $container->registerextension(new AcmeDemoExtension); $loader = new YamlFileLoader($container, new FileLocator( DIR )); $loader->load('config.yml'); $container->compile(); Quando si carica un file di configurazione che usa un alias di estensione come chiave, l'estensione deve essere già stata registrata nel costruttore di contenitore o verrà sollevata un'eccezione. I valori di tali sezioni dei file di configurazione sono passati al primo parametro del metodo load dell'estensione: Listing - public function load(array $configs, ContainerBuilder $container) $foo = $configs[0]['pippo']; //valoredipippo $bar = $configs[0]['pluto']; //valoredipluto Il parametro $configs è un array contenente ogni diverso file di configurazione caricato nel contenitore. Nell'esempio precedente viene caricato solo un unico file di configurazione, ma sarà comunque dentro un array. L'array sarà simile a questo: Listing - array( array( 'pippo' => 'valoredipippo', 'pluto' => 'valoredipluto', ), ) Sebbene sia possibile gestire manualmente la fusione dei vari file, è molto meglio usare il componente Config per fondere e validare i valori di configurazione. Usando il processo di configurazione si può accedere ai valori di configurazione in questo modo: Listing - 0 use Symfony\Component\Config\Definition\Processor; public function load(array $configs, ContainerBuilder $container) $configuration = new Configuration(); $processor = new Processor(); $config = $processor->processconfiguration($configuration, $configs); $foo = $config['pippo']; //valoredipippo $bar = $config['pluto']; //valoredipluto generated on August, 0 Chapter : Compilazione del contenitore
114 Ci sono altri due metodi da implementare. Uno per restituire lo spazio dei nomi XML, in modo che le parti rilevanti di un file di configurazione XML siano passate all'estensione. L'altro per specificare il percorso di base ai file XSD per validare la configurazione XML: Listing -0 public function getxsdvalidationbasepath() return DIR.'/../Resources/config/'; public function getnamespace() return ' La validazione XSD è facoltativa, restituendo false dal metodo getxsdvalidationbasepath sarà disabilitata. La versione XML della configurazione sarà dunque simile a questa: Listing - 0 <?xml version=".0"?> <container xmlns=" xmlns:xsi=" xmlns:acme_demo=" xsi:schemalocation=" symfony/schema/hello-.0.xsd"> <acme_demo:config> <acme_demo:pippo>valoredipippo</acme_hello:foo> <acme_demo:pluto>valoredipluto</acme_demo:bar> </acme_demo:config> </container> Nel framework Symfony c'è una classe base Extension, che implementa questi metodi e un metodo scorciatoia per processare la configurazione. Vedere Caricare la configurazione di un servizio in un bundle per maggiori dettagli. Il valore di configurazione processato ora può essere aggiunto come parametro del contenitore, come se fosse elencato nella sezione parameters del config, ma con il beneficio aggiuntivo di fondere file diversi e della validazione della configurazione: Listing - public function load(array $configs, ContainerBuilder $container) $configuration = new Configuration(); $processor = new Processor(); $config = $processor->processconfiguration($configuration, $configs); $container->setparameter('acme_demo.pippo', $config['pippo']) generated on August, 0 Chapter : Compilazione del contenitore
115 0 Si possono stabilire requisiti di configurazione più complessi nelle classi estensione. Per esempio, si può scegliere di caricare un file di configurazione principale, ma anche di carne uno secondario solo se un certo parametro è impostato: Listing - 0 public function load(array $configs, ContainerBuilder $container) $configuration = new Configuration(); $processor = new Processor(); $config = $processor->processconfiguration($configuration, $configs); $loader = new XmlFileLoader( $container, new FileLocator( DIR.'/../Resources/config') ); $loader->load('services.xml'); if ($config['advanced']) $loader->load('advanced.xml'); La registrazione di un'estensione nel contenitore non è sufficiente per includerla tra le estensioni processate durante la compilazione del contenitore. Caricare la configurazione che usa l'alias dell'estensione come chiave, come mostrato in precedenza, assicurerà il suo caricamento. Si può anche dire al costruttore di contenitore di caricarla, usando il metodo loadfromextension() : Listing - use Symfony\Component\DependencyInjection\ContainerBuilder; $container = new ContainerBuilder(); $extension = new AcmeDemoExtension(); $container->registerextension($extension); $container->loadfromextension($extension->getalias()); $container->compile(); Se si deve manipolare la configurazione caricata da un'estensione, non lo si può fare da un'altra estensione, perché usa un contenitore nuovo. Invece, si deve usare un passo di compilatore, che funziona con il contenitore dopo che le estensioni sono state processate. Prependere la configurazione passata all'estensione Una Extension può prependere la configurazione di un altro bundle, prima della chiamata al metodo load(), implementando PrependExtensionInterface : Listing generated on August, 0 Chapter : Compilazione del contenitore
116 0 use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; class AcmeDemoExtension implements ExtensionInterface, PrependExtensionInterface public function prepend() $container->prependextensionconfig($name, $config); Per maggiori dettagli, si veda Semplificare la configurazione di più bundle, che è specifica del framework Symfony, ma contiene più informazioni su questa caratteristica. Creare un passo di compilatore Si possono anche creare e registrare i propri passi di compilatore con il contenitore. Per creare un passo di compilatore, si deve implementare l'interfaccia CompilerPassInterface. Il compilatore offre la possibilità di manipolare le definizioni del servizio che sono state compilate. Questo può essere molto potente, ma non necessario nell'uso quotidiano. Il passo di compilatore deve avere il metodo process, che viene passato al contenitore che si sta compilando: Listing - 0 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; class CustomCompilerPass implements CompilerPassInterface public function process(containerbuilder $container) Si possono manipolare parametri e definizioni del contenitore, usando i metodi descritti in Lavorare con parametri e definizioni del contenitore. Un cosa che si fa solitamente in un passo di compilatore è la ricerca di tutti i servizi con determinato tag, in modo da poterli processare in qualche modo o collegarli dinamicamente in qualche altro servizio. Registrare un passo di compilatore Occorre registrare il proprio passo di compilatore con il contenitore. Il suo metodo process sarà richiamato quando il contenitore viene compilato: Listing -. generated on August, 0 Chapter : Compilazione del contenitore
117 use Symfony\Component\DependencyInjection\ContainerBuilder; $container = new ContainerBuilder(); $container->addcompilerpass(new CustomCompilerPass); I passi di compilatore sono registrati in modo diverso, se si usa il framework completo, si veda Lavorare con i passi di compilatore nei bundle per maggiori dettagli. Controllare l'ordine dei passi I passi di compilatore predefiniti sono raggruppati in passi di ottimizzazione e passi di rimozione. I passi di ottimizzazione girano prima e includono compiti come la risoluzione di riferimenti dentro le definizioni. I passi di rimozione eseguono compiti come la rimozione di alias privati e di servizi inutilizzati. Si può scegliere in quale ordine sia eseguito ogni passo aggiuntivo. Per impostazione predefinita, sono eseguiti prima dei passi di ottimizzazione. Si possono usare le seguenti costanti come secondo parametro quando si registra un passo con il contenitore, per controllare in quale posizione vada il passo: PassConfig::TYPE_BEFORE_OPTIMIZATION PassConfig::TYPE_OPTIMIZE PassConfig::TYPE_BEFORE_REMOVING PassConfig::TYPE_REMOVE PassConfig::TYPE_AFTER_REMOVING Per esempio, per eseguire il proprio passo dopo i passi di rimozione predefiniti: Listing - use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\PassConfig; $container = new ContainerBuilder(); $container->addcompilerpass( new CustomCompilerPass, PassConfig::TYPE_AFTER_REMOVING ); Esportare la configurazione per le prestazioni L'uso di file di configurazione per gestire il contenitore di servizi può essere molto più facile da capire rispetto all'uso di PHP, appena ci sono molti servizi. Questa facilità ha un prezzo, quando si considerano le prestazioni, perché i file di configurazione necessitano di essere analizzati, in modo da costruire la configurazione in PHP. Si possono prendere due piccioni con una fava, usando i file di configurazione e poi esportando e mettendo in cache la configurazione risultante. PhpDumper rende facile l'esportazione del contenitore compilato: Listing - use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; $file = DIR.'/cache/container.php'; generated on August, 0 Chapter : Compilazione del contenitore
118 0 if (file_exists($file)) require_once $file; $container = new ProjectServiceContainer(); else $container = new ContainerBuilder(); $container->compile(); $dumper = new PhpDumper($container); file_put_contents($file, $dumper->dump()); ProjectServiceContainer è il nome predefinito dato alla classe del contenitore esportata: lo si può cambiare tramite l'opzione class, al momento dell'esportazione: Listing -0 0 $file = DIR.'/cache/container.php'; if (file_exists($file)) require_once $file; $container = new MyCachedContainer(); else $container = new ContainerBuilder(); $container->compile(); $dumper = new PhpDumper($container); file_put_contents( $file, $dumper->dump(array('class' => 'MyCachedContainer')) ); Si otterrà la velocità del contenitore compilato in PHP con la facilità di usare file di configurazione. Inoltre, esportare il contenitore in questo modo ottimizza ulteriormente i servizi creati dal contenitore. Nell'esempio precedente, occorrerà pulire il contenitore in cache ogni volta che si fa una modifica. L'aggiunta di una variabile che determini se si è in modalità di debug consente di mantenere la velocità del contenitore in cache in produzione, mantenendo una configurazione aggiornata durante lo sviluppo dell'applicazione: Listing - 0 // impostare $isdebug in base a una logica del progetto $isdebug =...; $file = DIR.'/cache/container.php'; if (!$isdebug && file_exists($file)) require_once $file; $container = new MyCachedContainer(); else $container = new ContainerBuilder(); $container->compile(); if (!$isdebug) generated on August, 0 Chapter : Compilazione del contenitore
119 0 $dumper = new PhpDumper($container); file_put_contents( $file, $dumper->dump(array('class' => 'MyCachedContainer')) ); Si può fare un ulteriore miglioramento solo ricompilando il contenitore in modalità debug quando le modifiche sono state fatte alla sua configurazione, piuttosto che a ogni richiesta. Lo si può fare mettendo in cache i file risorse usati per configurare il contenitore, come descritto nella documentazione del componente config, "Cache basata sulle risorse". Non occorre calcolare quali file mettere in cache, perché il costruttore del contenitore tiene traccia di tutte le risorse usate per configurarlo, non solo dei file di configurazione, ma anche le classi estensione e i passi di compilatore. Ciò significa che qualsiasi modifica a uno di tali file invaliderà la cache e farà scattare la ricostruzione del contenitore. Basta chiedere al contenitore queste risorse e usarle come meta dati per la cache: Listing // impostare $isdebug in base a qualcosa nel progetto $isdebug =...; $file = DIR.'/cache/container.php'; $containerconfigcache = new ConfigCache($file, $isdebug); if (!$containerconfigcache->isfresh()) $containerbuilder = new ContainerBuilder(); $containerbuilder->compile(); $dumper = new PhpDumper($containerBuilder); $containerconfigcache->write( $dumper->dump(array('class' => 'MyCachedContainer')), $containerbuilder->getresources() ); require_once $file; $container = new MyCachedContainer(); Ora il contenitore in cache esportato viene usato indipendentemente dalla modalità di debug. La differenza è che ConfigCache è impostato a debug con il secondo parametro del suo costruttore. Quando la cache non è in debug, sarà sempre usato il contenitore in cache, se esiste. In debug, viene scritto un file aggiuntivo di meta dati, con i timestamp di tutti i file risorsa. Vengono poi verificate eventuali modifiche dei file, nel caso in cui la cache debba essere considerata vecchia. Nel framework completo, compilazione e messa in cache del contenitore sono eseguite automaticamente. generated on August, 0 Chapter : Compilazione del contenitore
120 Chapter Usare i tag nei servizi I tag sono generiche stinghe (con alcune opzioni) che si possono applicare a un servizio. Di per sé, i tag non alterano la funzionalità di un servizio in alcun modo. Ma, se lo si desidera, si può chiedere a un costruttore di contenitori una lista di tutti i servizi che hanno uno specifico tag. Questo può tornare utile nei passi di compilatore, in cui si possono trovare tali servizi e usarli per modificarli in qualche modo. Per esempio, se si usa SwiftMailer, si può immaginare di voler implementare una "catena di trasporto", che è un insieme di classi che implementano \Swift_Transport. Usando una catena, si vogliono offrire a SwiftMailer diversi modi di trasportare un messsaggio, finché uno non ha successo. Per iniziare, definire la classe TransportChain: Listing - 0 class TransportChain private $transports; public function construct() $this->transports = array(); public function addtransport(\swift_transport $transport) $this->transports[] = $transport; Quindi, definire la catena come servizio: Listing - services: acme_mailer.transport_chain: class: TransportChain generated on August, 0 Chapter : Usare i tag nei servizi 0
121 Definire servizi con un tag personalizzato Ora vogliamo che diverse classi \Swift_Transport siano istanziate e aggiunte alla catena automaticamente, usando il metodo addtransport(). Come esempio, aggiungere i seguenti trasporti come servizi: Listing - 0 services: acme_mailer.transport.smtp: class: \Swift_SmtpTransport arguments: - "%mailer_host%" tags: - name: acme_mailer.transport acme_mailer.transport.sendmail: class: \Swift_SendmailTransport tags: - name: acme_mailer.transport Si noti che a ognuno è stato assegnato il tag acme_mailer.transport. Questo è il tag personalizzato che useremo nel passo di compilatore. Il passo di compilatore è ciò che dà un significato a questo tag. Creare un CompilerPass Il passo di compilatore ora chiede al contenitore ogni servizio che abbia il tag personalizzato: Listing use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Reference; class TransportCompilerPass implements CompilerPassInterface public function process(containerbuilder $container) if (!$container->hasdefinition('acme_mailer.transport_chain')) return; $definition = $container->getdefinition( 'acme_mailer.transport_chain' ); $taggedservices = $container->findtaggedserviceids( 'acme_mailer.transport' ); foreach ($taggedservices as $id => $attributes) $definition->addmethodcall( 'addtransport', array(new Reference($id)) ); Il metodo process() verifica l'esistenza del servizio acme_mailer.transport_chain, quindi cerca tutti i servizi con tag acme_mailer.transport. Aggiunge all definizione del servizio acme_mailer.transport_chain una chiamata a addtransport() per ogni servizio generated on August, 0 Chapter : Usare i tag nei servizi
122 "acme_mailer.transport" trovato. Il primo parametro di ognuna di queste chiamate sarà il servizio di trasporto stesso. Registrare il passo con il contenitore Occorrerà anche registrare il passo con il contenitore, sarà poi eseguito quando il contenitore viene compilato: Listing - use Symfony\Component\DependencyInjection\ContainerBuilder; $container = new ContainerBuilder(); $container->addcompilerpass(new TransportCompilerPass()); I passi di compilatore sono registrati in modo diverso, se si usa il framework completo. Vedere Lavorare con i passi di compilatore nei bundle per maggiori dettagli. Aggiungere altri attributi ai tag A volte occorrono informazioni aggiuntive su ogni servizio che ha un certo tag. Per esempio, si potrebbe voler aggiungere un alias a ogni TransportChain. Per iniziare, cambiare la classe TransportChain: Listing class TransportChain private $transports; public function construct() $this->transports = array(); public function addtransport(\swift_transport $transport, $alias) $this->transports[$alias] = $transport; public function gettransport($alias) if (array_key_exists($alias, $this->transports)) return $this->transports[$alias]; Come si può vedere, al richiamo di addtransport, non prende solo un oggetto Swift_Transport, ma anche una stringa alias per il trasporto. Quindi, come si può fare in modo che ogni servizio di trasporto fornisca anche un alias? Per rispondere, cambiare la dichiarazione del servizio: Listing - generated on August, 0 Chapter : Usare i tag nei servizi
123 0 services: acme_mailer.transport.smtp: class: \Swift_SmtpTransport arguments: - "%mailer_host%" tags: - name: acme_mailer.transport, alias: pippo acme_mailer.transport.sendmail: class: \Swift_SendmailTransport tags: - name: acme_mailer.transport, alias: pluto Si noti che è stata aggiunta una chiave generica alias al tag. Per usarla effettivamente, aggiornare il compilatore: Listing use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Reference; class TransportCompilerPass implements CompilerPassInterface public function process(containerbuilder $container) if (!$container->hasdefinition('acme_mailer.transport_chain')) return; $definition = $container->getdefinition( 'acme_mailer.transport_chain' ); $taggedservices = $container->findtaggedserviceids( 'acme_mailer.transport' ); foreach ($taggedservices as $id => $tagattributes) foreach ($tagattributes as $attributes) $definition->addmethodcall( 'addtransport', array(new Reference($id), $attributes["alias"]) ); La parte più strana è la variabile $attributes. Poiché si può usare lo stesso tag più volte sullo stesso servizio (p.e. in teoria si potrebbe assegnare il tag acme_mailer.transport allo stesso servizio cinque volte, $attributes è un array di informazioni sul tag per ciascun tag su tale servizio. generated on August, 0 Chapter : Usare i tag nei servizi
124 Chapter Usare un factory per creare servizi New in version.: Il metodo setfactory() è stato introdotto in Symfony.. Fare riferimento alle versioni precedenti per la sintassi per i factory prima di.. Il contenitore di servizi di Symfony fornisce un modo potente per controllare la creazione di oggetti, consentendo di specificare parametri da passare al costruttore, così come chiamate a metodi e impostazioni di parametri. A volte, tuttavia, non fornisce tutto ciò che è necessario per costruire gli oggetti. Per tali situazioni, si può usare un factory per creare oggetti e dire al contenitore di servizi di richiamare un metodo del factory, invece che istanziare direttamente l'oggetto. Si supponga di avere un factory che configura e restituisce un nuovo oggetto NewsletterManager: Listing - 0 class NewsletterManagerFactory public static function createnewslettermanager() $newslettermanager = new NewsletterManager(); return $newslettermanager; Per rendere l'oggetto NewsletterManager disponibile come servizio, si può configurare il contenitore di servizi per usare il metodo factory NewsletterFactory::createNewsletterManager(): Listing - services: newsletter_manager: class: NewsletterManager factory: [NewsletterManagerFactory, createnewslettermanager] Ora il metodo sarà richiamato staticamente. Se il factory stesso va istanziato e il metodo dell'oggetto risultante richiamato, configurare il factory stesso come servizio: In questo caso, il metodo (p.e. get) va cambiato per non essere statico:. generated on August, 0 Chapter : Usare un factory per creare servizi
125 Listing - services: newsletter_manager.factory: class: NewsletterManagerFactory newsletter_manager: class: NewsletterManager factory: ["@newsletter_manager.factory", createnewslettermanager] Passare parametri al metodo del factory Se occorre passare parametri al metodo del factory, si può usare l'opzione arguments dentro al contenitore di servizi. Per esempio, si supponga che il metodo get dell'esempio precedente accetti un servizio templating come parametro: Listing - services: newsletter_manager.factory: class: NewsletterManagerFactory newsletter_manager: class: NewsletterManager factory: ["@newsletter_manager.factory", createnewslettermanager] arguments: - "@templating" generated on August, 0 Chapter : Usare un factory per creare servizi
126 Chapter Configurare servizi con un configuratore di servizi Il configuratore di servizi è una caratteristica del contenitore, che consente di usare un callable per configurare un servizio appena istanziato. Si può specificare un metodo di un altro servizio, una funzione PHP o un metodo statico in una classe. L'istanza del servizio viene passata al callable, consentendo al configuratore di fare tutto ciò di cui ha bisogno per configurare il servizio creato. Per esempio, si può usare un configuratore di servizi quando si ha un servizio che richiede una preparazione complessa, in base a impostazioni di configurazione provenienti da diversi sorgenti/servizi. Usando un configuratore esterno, si può mantenere l'implementazione del servizio pulita e disaccoppiata da altri oggetti, che forniscono la configurazione necessaria. Un altro caso d'uso interessante è quando si hanno molti oggetti che condividono una configurazione comune o che vanno configurati in modo simile a runtime. Per esempio, si supponga di avere un'applicazione in cui si inviano diversi tipi di agli utenti. Le passano attraverso diversi formattatori, che possono essere abilitati o meno, a seconda di alcune impostazioni dinamiche dell'applicazione. Si inizia definendo una classe NewsletterManager, come questa: Listing - 0 class NewsletterManager implements FormatterAwareInterface protected $mailer; protected $enabledformatters; public function setmailer(mailer $mailer) $this->mailer = $mailer; public function setenabledformatters(array $enabledformatters) $this->enabledformatters = $enabledformatters; generated on August, 0 Chapter : Configurare servizi con un configuratore di servizi
127 e una classe GreetingCardManager: Listing - 0 class GreetingCardManager implements FormatterAwareInterface protected $mailer; protected $enabledformatters; public function setmailer(mailer $mailer) $this->mailer = $mailer; public function setenabledformatters(array $enabledformatters) $this->enabledformatters = $enabledformatters; Come menzionato in precedenza, lo scopo è quello di impostare i formattatori a runtime, a seconda delle configurazioni dell'applicazione. Per farlo, definiamo anche una classe FormatterManager, che si occupi di caricare e validatore i formattatori abilitati nell'applicazione: Listing class FormatterManager protected $enabledformatters; public function loadformatters() // codice per configurare quali formattatori usare $enabledformatters = array(...); $this->enabledformatters = $enabledformatters; public function getenabledformatters() return $this->enabledformatters; Se lo scopo è quello di evitare accoppiamenti tra NewsletterManager e GreetingCardManager con FormatterManager, si potrebbe voler creare una classe configuratore, per configurare tali istanze: Listing - class Configurator private $formattermanager; generated on August, 0 Chapter : Configurare servizi con un configuratore di servizi
128 0 public function construct( formattermanager $formattermanager) $this->formattermanager = $formattermanager; public function configure( formatterawareinterface $ manager) $ manager->setenabledformatters( $this->formattermanager->getenabledformatters() ); Il compito di Configurator è iniettare i filtri abilitati in NewsletterManager e GreetingCardManager, perché non sono consapevoli di dove i filtri abilitati arrivino. D'altro canto, FormatterManager sa dei formattatori abilitati e come caricarli, mantenendo il principio della singola responsabilità. Configurazione del configuratore di servizi La configurazione del servizio per le classi viste sopra assomiglia a questa: Listing services: my_mailer: #... _formatter_manager: class: FormatterManager #... _configurator: class: Configurator arguments: ["@ _formatter_manager"] #... newsletter_manager: class: NewsletterManager calls: - [setmailer, ["@my_mailer"]] configurator: ["@ _configurator", configure] greeting_card_manager: class: GreetingCardManager calls: - [setmailer, ["@my_mailer"]] configurator: ["@ _configurator", configure] generated on August, 0 Chapter : Configurare servizi con un configuratore di servizi
129 Chapter Come gestire le dipendenze comuni con servizi genitori Quando si aggiungono molte funzionalità a un'applicazione, si potrebbe voler condividere delle dipendenze comuni tra classi correlate. Per esempio, si potrebbe avere un gestore di newsletter che usa un setter per impostare le sue dipendenze: Listing - 0 class NewsletterManager protected $mailer; protected $ formatter; public function setmailer(mailer $mailer) $this->mailer = $mailer; public function set formatter( formatter $ formatter) $this-> formatter = $ formatter; e poi dei biglietti di auguri, con una classe che condivide alcune dipendenze: Listing - class GreetingCardManager protected $mailer; protected $ formatter; public function setmailer(mailer $mailer) $this->mailer = $mailer; generated on August, 0 Chapter : Come gestire le dipendenze comuni con servizi genitori
130 0 public function set formatter( formatter $ formatter) $this-> formatter = $ formatter; La configurazione dei servizi di queste classe potrebbe essere qualcosa del genere: Listing - 0 services: my_mailer: #... my_ _formatter: #... newsletter_manager: class: NewsletterManager calls: - [setmailer, ["@my_mailer"]] - [set formatter, ["@my_ _formatter"]] greeting_card_manager: class: "GreetingCardManager" calls: - [setmailer, ["@my_mailer"]] - [set formatter, ["@my_ _formatter"]] Ci sono diverse ripetizioni, sia nelle classi che nella configurazione. Questo vuol dire che, se per esempio si cambiano le classi Mailer o Formatter per essere iniettate tramite costruttore, si avrà bisogno di aggiornare la configurazione in due punti. In modo simile, se fosse necessario cambiare i metodi setter, si avrebbe bisogno di farlo in entrambe le classi. Il tipico modo di trattare i metodi comuni di queste classi correlate sarebbe estrarli in una classe superiore: Listing - 0 abstract class MailManager protected $mailer; protected $ formatter; public function setmailer(mailer $mailer) $this->mailer = $mailer; public function set formatter( formatter $ formatter) $this-> formatter = $ formatter; Quindi NewsletterManager e GreetingCardManager possono estendere tale classe: Listing - generated on August, 0 Chapter : Come gestire le dipendenze comuni con servizi genitori 0
131 class NewsletterManager extends MailManager e: Listing - class GreetingCardManager extends MailManager In modo simile, il contenitore di servizi di Symfony supporta anche l'estensione di servizi nella configurazione, in modo da poter ridurre le ripetizioni, specificando un genitore per un servizio. Listing - 0 #... services: #... mail_manager: abstract: true calls: - [setmailer, ["@my_mailer"]] - [set formatter, ["@my_ _formatter"]] newsletter_manager: class: "NewsletterManager" parent: mail_manager greeting_card_manager: class: "GreetingCardManager" parent: mail_manager In questo contesto, avere un servizio parent implica che i parametri e le chiamate a metodi del servizio genitore andrebbero usati per i servizi figli. Specificatamente, i metodi setter definiti per il servizio genitore saranno richiamati all'istanza dei servizi figli. Se si rimuove la voce di configurazione parent, i servizi saranno ancora istanziati e estenderanno ancora la classe MailManager. La differenza è che, omettendo la voce di configurazione parent, si farà in modo che calls, definito nel servizio mail_manager, non sarà eseguito quando i servizi figli saranno istanziati. Gli attributi scope, abstract e tags sono sempre presi dal servizio figlio. La classe genitore è astratta, perché non andrebbe istanziata direttamente dal contenitore o passata in un altro servizio. Esiste puramente come "template" per altri servizi. Per questo può non avere class configurata, che provocherebbe un'eccezione per un servizio non astratto. Per poter risolvere dipendenze dei genitori, ContainerBuilder deve essere precedentemente compilato. Si veda Compilazione del contenitore per maggiori dettagli. generated on August, 0 Chapter : Come gestire le dipendenze comuni con servizi genitori
132 Negli esempi mostrati c'è una relazione simile tra servizi padre e figlio e classi padre e figlio sottostanti. Sebbene non sia detto che questo debba sempre essere il caso, si possono estrarre le parti comuni di definizioni simili di servizi in un servizio padre, senza ereditare anche una classe padre in PHP. Sovrascrivere le dipendenze del genitore A volte si potrebbe voler sovrascrivere la classe passata come dipendenza solo per un servizio figlio. Fortunatamente, aggiungendo la configurazione della chiamata al metodo per il servizio figlio, le dipendenze impostate dalla classe genitore saranno sovrascritte. Se quindi si ha bisogno di passare una dipendenza diversa, solo alla classe NewsletterManager, la configurazione potrebbe essere come la seguente: Listing #... services: #... my_alternative_mailer: #... mail_manager: abstract: true calls: - [setmailer, ["@my_mailer"]] - [set formatter, ["@my_ _formatter"]] newsletter_manager: class: "NewsletterManager" parent: mail_manager calls: - [setmailer, ["@my_alternative_mailer"]] greeting_card_manager: class: "GreetingCardManager" parent: mail_manager La classe GreetingCardManager riceverà le stesse dipendenze di prima, ma a NewsletterManager sarà passato il servizio my_alternative_mailer, invece di my_mailer. Non si possono sovrascrivere le chiamate a metodi. Dopo aver definito nuove chiamate a metodi nel servizio figlio, queste sono aggiunte all'insieme attuale di chiamate a metodi. Questo vuol dire che funzionerà quando il setter sovrascrive la proprietà corrente, ma non funzionerà come ci si aspetta quando il setter appende a dati esistenti (p.e. un metodo addfilters()). In questi casi, l'unica soluzione è non estendere il servizio padre e configurare il servizio, come si farebbe senza questa caratteristica. generated on August, 0 Chapter : Come gestire le dipendenze comuni con servizi genitori
133 Chapter Configurazione avanzata del contenitore Segnare i servizi come pubblici / privati Quando si definisce un servizio, di solito si vuole potervi accedere dall'interno di un'applicazione. Tali servizi sono chiamati "pubblici". Per esempio, il servizio doctrine registrato con il contenitore durante l'uso di DoctrineBundle è un servizio pubblico, accessibile tramite: Listing - $doctrine = $container->get('doctrine'); Ci sono tuttavia dei casi in cui non si desidera che un servizio sia pubblico. Di solito avviene quando un servizio è definito solo per essere usato come parametro da un altro servizio. Se si usa un servizio privato come parametro di più di un altro servizio, ciò provocherà un'istanza in linea (p.e. new PippoPlutoPrivato()) all'interno di quest'altro servizio, rendendola non disponibile pubblicamente a runtime. In parole povere: un servizio sarà privato quanto non si vuole che sia accessibile direttamente dal codice. Ecco un esempio: Listing - services: pippo: class: Esempio\Pippo public: false Essendo il servizio privato, non si può richiamare: Listing - $container->get('pippo'); Tuttavia, se un servizio è stato segnato come privato, gli si può comunque assegnare un alias (vedere sotto) per accedervi (tramite alias). generated on August, 0 Chapter : Configurazione avanzata del contenitore
134 I servizi sono predefiniti come pubblici. Servizi sintetici I servizi sintetici sono servizi che vengono iniettati nel contenitore, invece di essere creati dal contenitore stesso. Per esempio, se si usa il componente HttpKernel con il componente DependencyInjection, il servizio request è iniettato nel metodo ContainerAwareHttpKernel::handle(), quando entra nello scope della richiesta. Se non c'è una richiesta, la classe non esiste, quindi non può essere inclusa nella configurazione del contenitore. Inoltre, il servizio deve essere diverso per ogni sotto-richiesta nell'applicazione. Per creare un servizio sintetico, impostare synthetic a true: Listing - services: request: synthetic: true Come si può vedere, viene impostata solo l'opzione synthetic. Tutte le altre opzioni vengono solo usate per configurare il modo in cui un servizio viene creato dal contenitore. Non essendo il servizio creato dal contenitore, tali opzioni sono omesse. Si può ora iniettare la classe, usando Container::set : Listing - $container->set('request', new MyRequest(...)); Alias A volte si ha bisogno di usare scorciatoie per accedere ad alcuni servizi. Si possono impostare degli alias e si può anche impostare un alias su un servizio non pubblico. Listing - services: pippo: class: Esempio\Pippo pluto: alias: pippo Ciò vuol dire che, quando si usa direttamente il contenitore, si può accedere al servizio pippo richiedendo il servizio pluto, in questo modo: Listing - $container->get('pluto'); // restituisce il servizio pippo. generated on August, 0 Chapter : Configurazione avanzata del contenitore
135 In YAML, si può anche usare una scorciatoia come alias di un servizio: Listing - services: pippo: class: Esempio\Pippo pluto: "@pippo" Richiesta di file Possono esserci dei casi in cui occorra includere altri file subito prima che il servizio stesso sia caricato. Per poterlo fare, si può usare la direttiva file. Listing - services: foo: class: Esempio\Pippo\Pluto file: "%kernel.root_dir%/src/percorso/del/file/pippo.php" Si noti che Symfony richiamerà internamente la funzione require_once di PHP, il che vuol dire che il file sarà incluso una sola volta per richiesta. Decorare i servizi New in version.: I servizi decorati sono stati introdotti in Symfony.. Quando si sovrascrive una definizione esistente, il vecchio servizio va perduto: Listing -0 $container->register('pippo', 'ServizioPippo'); // questo rimpiazzerà la vecchia definizione con quella nuova // la vecchia definizione va perduta $container->register('pippo', 'NuovoServizioPippo'); La maggior parte delle volte questo è esattamente quello che si desidera. A volte, però, si potrebbe invece voler decorare il vecchio servizio. In questo caso, il vecchio servizio viene mantenuto, per potervi fare riferimento all'interno del nuovo. Questa configurazione sostituisce pippo con un nuovo servizio, ma mantiene un riferimento al vecchio, come pluto.inner: Listing - bar: public: false class: stdclass decorates: pippo arguments: ["@pluto.inner"] Ecco quello che succede: il metodo setdecoratedservice()` dice al contenitore che il servizio ``pluto sostituisce il servizio pippo, rinominando pippo in pluto.inner. Per convenzione, il vecchio servizio pippo è rinominato pluto.inner, in modo da poterlo iniettare nel nuovo servizio. generated on August, 0 Chapter : Configurazione avanzata del contenitore
136 L'identificativo interno generato è basato sull'id del servizio generato (pluto, in questo caso), non su quello del servizio decorato (pippo, in questo caso). Questo è necessario, per consentire più decoratori sullo stesso servizio (devono avere id generati diversi). La maggior parte delle volte, il decoratore deve essere dichiarato privato, perché non ci sarà bisogno di recuperarlo come pluto dal contenitore. La visibilità del servizio edcorato pippo (che è un alias per pluto) resterà quella originale di pippo. Si può cambiare il nome del servizio interno, se lo si desidera: Listing - bar: class: stdclass public: false decorates: pippo decoration_inner_name: pluto.wooz arguments: ["@pluto.wooz"] generated on August, 0 Chapter : Configurazione avanzata del contenitore
137 Chapter 0 Servizi pigri New in version.: I servizi pigri sono stati aggiunti in Symfony.. Perché i servizi pigri? A volte può essere necessario iniettare, all'interno del proprio oggetto, un servizio un po' pesante da istanziare e che non sempre viene utilizzato. Si supponga, ad esempio, di avere un GestoreDiNewsletter e che si voglia iniettare un servizio mailer al suo interno. Solo alcuni metodi del GestoreDiNewsletter usano effettivamente il mailer ma, anche senza farne uso, il servizio mailer verrebbe comunque istanziato in modo da poter costruire il GestoreDiNewsletter. Per risolvere questo problema è possibile usare un servizio pigro. Quando si usa un servizio pigro, in realtà viene iniettato un "proxy" del servizio mailer. Il proxy sembra e si comporta esattamente come se fosse il mailer, a parte il fatto che mailer non viene istanziato finché non si interagisce in qualche modo con il suo proxy. Installazione Per poter istanziare i servizi pigri è prima necessario installare il bridge ProxyManager : Listing 0- $ composer require symfony/proxy-manager-bridge:~. Se si usa il framework completo, questo pacchetto è già incluso, ma il vero gestore di proxy deve essere incluso. Eseguire quindi: Listing 0- $ php composer.phar require ocramius/proxy-manager:~0. Compilare quindi il contenitore e verificare di avere un proxy per i servizi pigri.. generated on August, 0 Chapter 0: Servizi pigri
138 Configurazione Si può definire un servizio come pigro, modificandone la definizione: Listing 0- services: pippo: class: Acme\Pippo lazy: true Ora è possibile richiedere il servizio dal contenitore: Listing 0- $servizio = $container->get('pippo'); A questo punto il $servizio recuperato dovrebbe essere un proxy virtuale con la stessa firma della classe che rappresenta il servizio. È anche possibile iniettare il servizio così come si farebbe con qualsiasi altro servizio. L'oggetto che verrà effettivamente iniettato sarà il proxy. Per verificare che il proxy funzioni, si può semplicemente verificare l'interfaccia dell'oggetto ricevuto. Listing 0- var_dump(class_implements($service)); Se la classe implementa ProxyManager\Proxy\LazyLoadingInterface, i servizi pigri stanno funzionando. Se non si è installato il bridge ProxyManager, il contenitore si limiterà a saltare il parametro lazy e a istanziare il servizio come farebbe normalmente. Il proxy viene inizializzato e il servizio vero e proprio viene istanziato non appena si dovesse interagire con l'oggetto. Risorse aggiuntive È possibile approfondire le modalità con cui i sostituti vengono istanziati, generati e inizializzati nella documentazione su ProxyManager generated on August, 0 Chapter 0: Servizi pigri
139 Chapter Flusso di costruzione del contenitore Nelle pagine precedenti di questa sezioni, è stato detto poco sulle posizioni in cui i vari file e le classi dovrebbero trovarsi. Questo perché ciò dipende dall'applicazione, libreria o framework in cui si vuole usare il contenitore. Vedere come il contenitore è configurato e costruito nel framework Symfony aiuterà a capire come tutti questi file si incastrino insieme, sia che si voglia usare il framework, sia che si cerchi solo di usare il contenitore di servizi in un'altra applicazione. Il framework usa il componente HttpKernel per gestire il caricamento della configurazione del contenitore di servizi dall'applicazione e dai bundle, inoltre gestisce la compilazione e la cache. Anche se non si usa HttpKernel, dovrebbe dare un'idea del modo in cui organizzare la configurazione in un'applicazione modulare. Lavorare con il contenitore in cache Il kernel verifica se c'è una versione in cache del contenitore, prima di costruirlo. HttpKernel ha un'impostazione di debug, per cui la versione in cache viene usata se tale impostazione vale false. Se invece debug è true, il kernel verifica se la configurazione è fresca e, se lo è, la versione in cache è quella del contenitore. Se non lo è, il contenitore viene costruito a partire dalla configurazione a livello di applicazione e da quella dei bundle. Leggere esportare la configurazione per le prestazioni per maggiori dettagli. Configurazione a livello di applicazione La configurazione a livello di applicazione è caricata dalla cartella app/config. Vengono caricati più file e quindi fusi, quando le estensioni vengono processate. Ciò consente di avere configurazioni diverse per ambienti diversi, p.e. dev, prod, ecc. Questi file contengono parametri e servizi, che sono caricati direttamente nel contenitore, come in impostare il contenitore con file di configurazione. Contengono anche configurazioni che sono processate da estensioni, come in gestire la configurazione con le estensioni. Sono considerate configurazioni di bundle, perché ogni bundle contiene una classe an Extension. generated on August, 0 Chapter : Flusso di costruzione del contenitore
140 Configurazione a livello di bundle Per convenzione, ogni bundle contiene una classe Extension, nella cartella DependencyInjection del bundle stesso. Queste classi vengono registrare da ContainerBuilder, al boot del kernel. Quando ContainerBuilder viene compilato, la configurazione a livello di applicazione rilevante per l'estensione del bundle viene passata alla classe Extension, che solitamente carica anche i propri file di configurazione, tipicamente dalla cartella Resources/config del bundle. La configurazione a livello di applicazione è solitamente processata con un oggetto Configuration, anch'esso memorizzato nella cartella DependencyInjection del bundle. Passi di compilatore per consentire interazioni tra bundle I passi di compilatore sono usati per consentire interazioni tra diversi bundle, poiché non possono influire a vicenda sulla configurazione nelle classi estensione. Uno degli usi principali è il processamento dei servizi con tag, consentendo ai bundle di registrare servizi che possono essere presi da altri bundle, come i logger di Monolog, le estensioni di Twig e i collettori di dati del Web Profiler. I passi di compilatore sono solitamente posti nella cartella DependencyInjection/Compiler del bundle. Compilazione e cache Dopo che il processo di compilazione ha caricato i servizi dalla configurazione, dalle estensioni e dai passi di compilatore, viene esportato, in modo che possa essere usata la cache nella volta successiva. La versione esportata è usata nelle richieste successive, essendo più efficiente. generated on August, 0 Chapter : Flusso di costruzione del contenitore 0
141 Chapter Il componente DomCrawler Il componente DomCrawler semplifica la navigazione nel DOM dei documenti HTML e XML. Il componente DomCrawler non è progettato per manipolare il DOM o per ri-esportare HTML/ XML, anche se sarebbe tecnicamente possibile utilizzarlo in tal modo. Installazione È possibile installare il componente in due modi: Installandolo via Composer (symfony/dom-crawler su Packagist ). Utilizzando il repository ufficiale su Git ( ); Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Utilizzo La classe Crawler mette a disposizione metodi per effettuare query e manipolare i documenti HTML e XML. Un'istanza di Crawler rappresenta un insieme (SplObjectStorage ) di oggetti DOMElement, che sono, in pratica, nodi facilmente visitabili: generated on August, 0 Chapter : Il componente DomCrawler
142 Listing - 0 use Symfony\Component\DomCrawler\Crawler; $html = <<<'HTML' <!DOCTYPE html> <html> <body> <p class="messaggio">ciao Mondo!</p> <p>ciao Crawler!</p> </body> </html> HTML; $crawler = new Crawler($html); foreach ($crawler as $elementodom) print $elementodom->nodename; Le classi specializzate Link e Form sono utili per interagire con collegamenti html e i form durante la visita dell'albero HTML. DomCrawler proverà a sistemare automaticamente il codice HTML, per farlo corrispondere alle specifiche ufficiali. Per esempio, se si inserisce un tag <p> dentro a un altro tag <p>, sarà spostato come fratello del tag genitore. Questo è il comportamento atteso e fa parte delle specifiche di HTML. Se però si ottiene un comportamento inatteso, potrebbe esserne una causa. Pur non essendo DomCrawler pensato per esportare contenuti, si può vedere la versione "sistemata" del codice HTML con un'esportazione. Filtrare i nodi È possibile usare facilmente le espressioni di XPath: Listing - $crawler = $crawler->filterxpath('descendant-or-self::body/p'); internamente viene usato DOMXPath::query per eseguire le query XPath. La ricerca è anche più semplice se si è installato il componente CssSelector. In questo modo è possibile usare lo stile jquery per l'attraversamento: Listing - $crawler = $crawler->filter('body > p'); È possibile usare funzioni anonime per eseguire filtri complessi: Listing - use Symfony\Component\DomCrawler\Crawler; $crawler = $crawler. generated on August, 0 Chapter : Il componente DomCrawler
143 ->filter('body > p') ->reduce(function (Crawler $node, $i) // filtra i nodi pari return ($i % ) == 0; ); Per rimuovere i nodi, la funzione anonima dovrà restituire false. Tutti i metodi dei filtri restituiscono una nuova istanza di Crawler contenente gli elementi filtrati. Entrambio i metodi filterxpath() e filter() 0 funzionano con gli spazi di nomi XML, che possono essere scoperti autometicamente oppure registrati esplicitamente. New in version.: La scoperta automatica e la registrazione esplicita di spazi di nomi è stata introdotta in Symfony.. Si consideri il seguente XML: Listing - 0 <?xml version=".0" encoding="utf-"?> <entry xmlns=" xmlns:media=" xmlns:yt=" > <id>tag:youtube.com,00:video:kgzrzmecj</id> <yt:accesscontrol action="comment" permission="allowed"/> <yt:accesscontrol action="videorespond" permission="moderated"/> <media:group> <media:title type="plain">chordates - CrashCourse Biology #</media:title> <yt:aspectratio>widescreen</yt:aspectratio> </media:group> </entry> Lo si può filtrare con Crawler, senza bisogno di registrare alias di spazi di nomi, con filterxpath() : Listing - $crawler = $crawler->filterxpath('//default:entry/media:group//yt:aspectratio'); e con filter() : Listing - use Symfony\Component\CssSelector\CssSelector; CssSelector::disableHtmlExtension(); $crawler = $crawler->filter('default entry media group yt aspectratio'); generated on August, 0 Chapter : Il componente DomCrawler
144 Lo spazio dei nomi predefinito è registrato con prefisso "default". Lo si può cambiare con il metodo setdefaultnamespaceprefix(). Lo spazio dei nomi predefinito viene rimosso durante il caricamento del contenuto, se è l'unico spazio di nomi nel documento. Questo per semplificare le query xpath. Si possono registrare esplicitamente spazi di nomi con il metodo registernamespace() : Listing - $crawler->registernamespace('m', ' $crawler = $crawler->filterxpath('//m:group//yt:aspectratio'); Per una query su XML con selettore CSS, occorre disabilitare l'estensione HTML, tramite CssSelector::disableHtmlExtension, per evitare di convertire il selettore in minuscolo. Attraversamento dei nodi Accedere ai nodi tramite la loro posizione nella lista: Listing - $crawler->filter('body > p')->eq(0); Ottenere il primo o l'ultimo nodo della selezione: Listing -0 $crawler->filter('body > p')->first(); $crawler->filter('body > p')->last(); Ottenere i nodi allo stesso livello della selezione attuale: Listing - $crawler->filter('body > p')->siblings(); Ottenere i nodi, allo stesso livello, precedenti o successivi alla selezione attuale: Listing - $crawler->filter('body > p')->nextall(); $crawler->filter('body > p')->previousall(); Ottenere tutti i nodi figlio o padre: Listing - $crawler->filter('body')->children(); $crawler->filter('body > p')->parents(); Tutti i metodi di attraversamento restituiscono un nuova istanza di Crawler generated on August, 0 Chapter : Il componente DomCrawler
145 Accedere ai nodi tramite il loro valore New in version.: Il metodo nodename() è stato introdotto in Symfony.. Accedere al nome del nodo (nome del tag HTML) del primo nodo della selezione attuale (es. "p" o "div"): Listing - // restituirà il nome del nodo (nome del tag HTML) del primo elemento figlio di <body> $tag = $crawler->filterxpath('//body/*')->nodename(); Accedere al valore del primo nodo della selezione attuale: Listing - $message = $crawler->filterxpath('//body/p')->text(); Accedere al valore dell'attributo del primo nodo della selezione attuale: Listing - $class = $crawler->filterxpath('//body/p')->attr('class'); Estrarre l'attributo e/o il valore di un nodo da una lista di nodi: Listing - $attributi = $crawler ->filterxpath('//body/p') ->extract(array('_text', 'class')) ; L'attributo speciale _text rappresenta il valore di un nodo. Chiamare una funzione anonima su ogni nodo della lista: Listing - use Symfony\Component\DomCrawler\Crawler; $valorinodi = $crawler->filter('p')->each(function (Crawler $nodo, $i) return $nodo->text(); ); New in version In: Symfony., alle funzioni Closure each e reduce viene passato un Crawler come primo parametro. In precedenza, tale parametro era un DOMNode. La funzione anonima riceve la posizione e il nodo (come Crawler) come parametri. Il risultato è un array contenente i valori restituiti dalle chiamate alla funzione anonima. Aggiungere contenuti Il crawler supporta diversi modi per aggiungere contenuti: Listing - $crawler = new Crawler('<html><body /></html>'); $crawler->addhtmlcontent('<html><body /></html>'); $crawler->addxmlcontent('<root><node /></root>');. generated on August, 0 Chapter : Il componente DomCrawler
146 0 $crawler->addcontent('<html><body /></html>'); $crawler->addcontent('<root><node /></root>', 'text/xml'); $crawler->add('<html><body /></html>'); $crawler->add('<root><node /></root>'); Quando si trattano set di caratteri diversi da ISO--, aggiungere sempre il content HTML, usando il metodo addhtmlcontent(), in cui si può specificare come secondo parametro il set di caratteri desiderato. Essendo l'implementazione del Crawler basata sull'estensione di DOM, è anche possibile interagire con le classi native DOMDocument 0, DOMNodeList e DOMNode : Listing -0 0 $documento = new \DOMDocument(); $documento->loadxml('<root><node /><node /></root>'); $listanodi = $documento->getelementsbytagname('node'); $nodo = $documento->getelementsbytagname('node')->item(0); $crawler->adddocument($documento); $crawler->addnodelist($listanodi); $crawler->addnodes(array($nodo)); $crawler->addnode($nodo); $crawler->add($documento); Manipolare ed esportare un Crawler Questi metodi di Crawler servono per popolare inizialmente il proprio Crawler e non per essere usati per manipolare ulteriormente un DOM (sebbene sia possibile). Tuttavia, poiché il Crawler è un insieme di oggetti DOMElement, si può usare qualsiasi metodo o proprietà disponibile in DOMElement, DOMNode o DOMDocument. Per esempio, si può ottenere l'html di un Crawler con qualcosa del genere: Listing - $html = ''; foreach ($crawler as $domelement) $html.= $domelement->ownerdocument->savehtml($domelement); Oppure si può ottenere l'html del primo nodo con html() : Listing - $html = $crawler->html(); Il metodo html è nuovo in Symfony generated on August, 0 Chapter : Il componente DomCrawler
147 Collegamenti Per trovare un collegamento tramite il suo nome (o un'immagine cliccabile tramite il suo attributo alt) si usa il metodo selectlink in un crawler esistente. La chiamata restituisce un'istanza di Crawler contenente solo i collegamenti selezionati. La chiamata link() restituisce l'oggetto speciale Link : Listing - $linkscrawler = $crawler->selectlink('vai altrove...'); $link = $linkscrawler->link(); // oppure, in una sola riga $link = $crawler->selectlink('vai altrove...')->link(); L'oggetto Link selezionato: ha diversi metodi utili per avere ulteriori informazioni relative al collegamento Listing - // restituisce l'uri che può essere usato per eseguire nuove richieste $uri = $link->geturi(); Il metodo geturi() è specialmente utile, perché pulisce il valore di href e lo trasforma nel modo in cui dovrebbe realmente essere processato. Per esempio, un collegamento del tipo href="#foo" restituirà l'uri completo della pagina corrente con il suffisso #foo. Il valore restituito da geturi() è sempre un URI completo, sul quale è possibile lavorare. Form Un trattamento speciale è riservato anche ai form. È disponibile, in Crawler, un metodo selectbutton() che restituisce un altro Crawler relativo al pulsante (input[type=submit], input[type=image], o button) con il testo dato. Questo metodo è specialmente utile perché può essere usato per restituire un oggetto Form 0, che rappresenta il form all'interno del quale il pulsante è definito: Listing - $form = $crawler->selectbutton('valida')->form(); // oppure "riempire" i campi del form con dati $form = $crawler->selectbutton('valida')->form(array( 'nome' => 'Ryan', )); L'oggetto Form ha molti utilissimi metodi che permettono di lavorare con i form: $uri = $form->geturi(); $metodo = $form->getmethod(); Il metodo geturi() fa più che restituire il mero attributo action del form. Se il metodo del form è GET, allora, imitando il comportamento del browser, restituirà l'attributo dell'azione seguito da una stringa di tutti i valori del form. È possibile impostare e leggere virtualmente i valori nel form: generated on August, 0 Chapter : Il componente DomCrawler
148 Listing - 0 // imposta, internamente, i valori del form $form->setvalues(array( 'registrazione[nomeutente]' => 'fandisymfony', 'registrazione[termini]' =>, )); // restituisce un array di valori in un array "semplice", come in precedenza $values = $form->getvalues(); // restituisce i valori come li vedrebbe PHP // con "registrazione" come array $values = $form->getphpvalues(); Per lavorare con i campi multi-dimensionali: Listing - <form> <input name="multi[]" /> <input name="multi[]" /> <input name="multi[dimensionale]" /> </form> È necessario specificare il nome pienamente qualificato del campo: Listing - // Imposta un singolo campo $form->setvalue('multi[0]', 'valore'); // Imposta molteplici campi in una sola volta $form->setvalues(array('multi' => array( => 'valore', 'dimensionale' => 'un altro valore' ))); Se questo è fantastico, il resto è anche meglio! L'oggetto Form permette di interagire con il form come se si usasse il browser, selezionando i valori dei radio, spuntando i checkbox e caricando file: Listing - 0 $form['registrazione[nomeutente]']->setvalue('fandisymfony'); // cambia segno di spunta a un checkbox $form['registrazione[termini]']->tick(); $form['registrazione[termini]']->untick(); // seleziona un'opzione $form['registrazione[data_nascita][anno]']->select(); // seleziona diverse opzioni da una lista di opzioni o da una serie di checkbox $form['registrazione[interessi]']->select(array('symfony', 'biscotti')); // può anche imitare l'upload di un file $form['registrazione[foto]']->upload('/percorso/al/file/lucas.jpg'); Usare i dati del form A cosa serve tutto questo? Se si stanno eseguendo i test interni, è possibile recuperare informazioni da tutti i form esattamente come se fossero stati inviati utilizzando i valori PHP: Listing -0 generated on August, 0 Chapter : Il componente DomCrawler
149 $valori = $form->getphpvalues(); $files = $form->getphpfiles(); Se si utilizza un client HTTP esterno, è possibile usare il form per recuperare tutte le informazioni necessarie per create una richiesta POST dal form: Listing - $uri = $form->geturi(); $metodo = $form->getmethod(); $valori = $form->getvalues(); $files = $form->getfiles(); // a questo punto si usa un qualche client HTTP e si inviano le informazioni Un ottimo esempio di sistema integrato che utilizza tutte queste funzioni è Goutte. Goutte usa a pieno gli oggetti del Crawler di Symfony e, con essi, può inviare i form direttamente: Listing - 0 use Goutte\Client; // crea una richiesta a un sito esterno $client = new Client(); $crawler = $client->request('get', ' // seleziona il form e riempie alcuni valori $form = $crawler->selectbutton('entra')->form(); $form['login'] = 'fandisymfony'; $form['password'] = 'unapassword'; // invia il form $crawler = $client->submit($form); Scegliere valori non validi New in version.: Il metodo disablevalidation() è stato aggiunto in Symfony.. Per impostazione predefinita, i campi di scelta (select, radio) hanno una validazione interna, che previene l'impostazione di valori non validi. Se si vuole poter impostare valori non validi, si può usare il metodo disablevalidation(), sia sull'intero form, sia su campi specifici: Listing - // Disabilita la validazione per un campo specifico $form['country']->disablevalidation()->select('valore non valido'); // Disabilita la validazione per l'intero form $form->disablevalidation(); $form['country']->select('valore non valido');. generated on August, 0 Chapter : Il componente DomCrawler
150 Chapter Il componente EventDispatcher Il componente EventDispatcher fornisce strumenti che consentono ai componenti di un'applicazione di comunicare tra di loro, distribuendo eventi e ascoltandoli. Introduzione L'approccio orientato agli oggetti da tempo assicura estensibilità del codice. Creando classi con responsabilità ben definite, il codice diventa più flessibile, consentendo allo sviluppatore di estenderlo con sotto-classi, che ne modifichino il comportamento. Se tuttavia si vogliono condividere le proprie modifiche con altri sviluppatori, che a loro volta abbiano costruito le proprie sotto-classi, l'ereditarietà non è più la risposta giusta. Consideriamo un esempio concreato, in cui si voglia fornire un sistema di plugin per un progetto. Un plugin dovrebbe essere in grado di aggiungere metodi o di fare qualcosa prima o dopo che un metodo sia eseguito, senza interferire con altri plugin. Questo problema non si risolve facilmente con l'ereditarietà singola, mentre l'ereditarietà multipla (dove sia possibile con PHP) ha i suoi difetti. Il componente Event Dispatcher di Symfony implementa il pattern Mediator in modo semplice ed efficace, per rendere possibile tutto questo e per rendere un progetto veramente estensibile. Si prenda un semplice esempio da Il componente HttpKernel. Una volta creato un oggetto Response, può essere utile consentirne la modifica ad altri elementi del sistema (p.e. aggiungere header di cache) prima del suo utilizzo effettivo. Per poterlo fare, il kernel di Symfony lancia un evento, kernel.response. Ecco come funziona: Un ascoltatore (oggetto PHP) dice a un oggetto distributore centrale che vuole ascoltare l'evento kernel.response; A un certo punto, il kernel di Symfony dice all'oggetto distributore di distribuire l'evento kernel.response, passando un oggetto Event, che ha accesso all'oggetto Response; Il distributore notifica a (ovvero chiama un metodo su) tutti gli ascoltatori dell'evento kernel.response, consentendo a ciascuno di essi di modificare l'oggetto Response.. generated on August, 0 Chapter : Il componente EventDispatcher 0
151 Installazione Si può installare il componente in due modi: Installarlo via Composer (symfony/event-dispatcher su Packagist ); Usare il repository ufficiale su Git ( ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Uso Eventi Quando un evento viene distribuito, è identificato da un nome univoco (p.e. kernel.response), che può essere ascoltato da un numero qualsiasi di ascoltatori. Inoltre, un'istanza di Event viene creata e passata a tutti gli ascoltatori. Come si vedrà più avanti, l'oggetto Event stesso spesso contiene dei dati sull'evento distribuito. Convenzioni sui nomi Il nome univoco dell'evento può essere una stringa qualsiasi, ma segue facoltativamente alcune semplici convenzioni di nomenclatura: usa solo lettere minuscole, numeri, punti (.) e trattini bassi (_); ha un prefisso con uno spazio dei nomi, seguito da un punto (p.e. kernel.); termina con un verbo, che indica l'azione intrapresa (p.e. request). Ecco alcuni buoni esempi di nomi di eventi: kernel.response form.pre_set_data Nomi di eventi e oggetti Event Quando il distributore notifica gli ascoltatori, passa loro un oggetto Event. La classe base Event è molto semplice: contiene un metodo per fermare la propagazione degli eventi, non molto di più. Spesso, i dati su uno specifico evento devono essere passati insieme all'oggetto Event, in modo che gli ascoltatori ottengano le informazioni necessarie. Nel caso dell'evento kernel.response, l'oggetto Event creato e passato a ciascun ascoltatore è in effetti di tipo FilterResponseEvent, una sottoclasse dell'oggetto base Event. Questa classe contiene metodi come getresponse e setresponse, che consentono agli ascoltatori di ottenere, o anche sostituire, l'oggetto Response. La morale della favola è questa: quando si crea un ascoltatore per un evento, l'oggetto Event passato all'ascoltatore può essere una speciale sotto-classe, con metodi aggiuntivi per recuperare informazioni dall'evento e per rispondere all'evento generated on August, 0 Chapter : Il componente EventDispatcher
152 Il distributore Il distributore è l'oggetto centrale del sistema di distribuzione degli eventi. In generale, viene creato un solo distributore, che mantiene un registro di ascoltatori. Quando un evento viene distribuito dal distributore, esso notifica a tutti gli ascoltatori registrati a tale evento: Listing - use Symfony\Component\EventDispatcher\EventDispatcher; $dispatcher = new EventDispatcher(); Connettere gli ascoltatori Per sfruttare un evento esistente, occorre connettere un ascoltatore al distributore, in modo che riceva una notifica quando l'evento viene distribuito. Una chiamata al metodo addlistener() del distributore associa un qualsiasi callable PHP a un evento: Listing - $listener = new AcmeListener(); $dispatcher->addlistener('pippo.action', array($listener, 'onpippoaction')); Il metodo addlistener() accetta fino a tre parametri: Il nome dell'evento (stringa) che questo ascoltatore vuole ascoltare; Un callable PHP, che sarà notificato quando viene lanciato un evento che sta ascoltando; Un intero opzionale di priorità (più alto equivale a più importante, quindi l'ascoltatore scatterà prima), che determina quando far scattare un ascoltatore, rispetto ad altri (predefinito a 0). Se due ascoltatori hanno la medesima priorità, sono eseguiti nell'ordine in cui sono stati aggiunti al distributore. Un callable PHP è una variabile PHP che possa essere usata dalla funzione call_user_func() e che restituisca true se passata alla funzione is_callable(). Può essere un'istanza di \Closure, un oggetto che implementi un metodo invoke (che è ciò che in effetti sono le closure), una stringa che rappresenti una funzione, o infine un array che rappresenti il metodo di un oggetto o di una classe. Finora, abbiamo visto che oggetti PHP possano essere registrati come ascoltatori. Si possono anche registrare Closure PHP come ascoltatori di eventi: Listing - use Symfony\Component\EventDispatcher\Event; $dispatcher->addlistener('pippo.action', function (Event $event) // sarà eseguito quando l'evento pippo.action sarà distribuito ); Una volta registrato un evento sul distributore, esso aspetterà finché l'evento non sarà notificato. Nell'esempio precedente, quando l'evento pippo.action viene distribuito, il distributore richiama il metodo AcmeListener::onPippoAction e passa l'oggetto Event come singolo parametro: Listing - use Symfony\Component\EventDispatcher\Event; class AcmeListener. generated on August, 0 Chapter : Il componente EventDispatcher
153 0 public function onpippoaction(event $event) fare qualcosa In molti casi, viene passata all'ascoltatore una speciale sotto-classe Event, che è specifica dell'evento dato. Questo dà accesso all'ascoltatore a informazioni speciali sull'evento. Leggere la documentazione o l'implementazione di ciascun evento, per determinare l'esatta istanza Symfony\Component\EventDispatcher\Event passata. Per esempio, l'evento kernel.response passa un'istanza di Symfony\Component\HttpKernel\Event\FilterResponseEvent: Listing - use Symfony\Component\HttpKernel\Event\FilterResponseEvent; public function onkernelresponse(filterresponseevent $event) $response = $event->getresponse(); $request = $event->getrequest(); generated on August, 0 Chapter : Il componente EventDispatcher
154 Registrare ascoltatori di eventi nel contenitore di servizi Quando si usa ContainerAwareEventDispatcher e il componente DependencyInjection, si può usare RegisterListenersPass del componente HttpKernel per assegnare il tag di ascoltatore di eventi ai servizi: Listing use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass; $containerbuilder = new ContainerBuilder(new ParameterBag()); $containerbuilder->addcompilerpass(new RegisterListenersPass()); // registra il servizio come sottoscrittore di eventi $containerbuilder->setdefinition('event_dispatcher', new Definition( 'Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher', array(new Reference('service_container')) )); // registra il servizio come ascoltatore di eventi $listener = new Definition('AcmeListener'); $listener->addtag('kernel.event_listener', array( 'event' => 'foo.action', 'method' => 'onfooaction', )); $containerbuilder->setdefinition('listener_service_id', $listener); // registra un sottoscrittore di eventi $subscriber = new Definition('AcmeSubscriber'); $subscriber->addtag('kernel.event_subscriber'); $containerbuilder->setdefinition('subscriber_service_id', $subscriber); Per impostazione predefinita, RegisterListenersPass presume che l'id del servizio del distributore di eventi sia event_dispatcher, che gli ascoltatori di eventi abbiano il tag kernel.event_listener e che i sottoscrittori di eventi abbiano il tag kernel.event_subscriber. Si possono cambiare tali valori predefiniti passando valori personalizzati al costruttore di RegisterListenersPass. Creare e distribuire un evento Oltre a registrare ascoltatori con eventi esistenti, si possono creare e distribuire i propri eventi. Questo è utile quando si creano librerie di terze parti e anche quando si vogliono mantenere i vari componenti dei propri sistemi flessibili e disaccoppiati. La classe statica Events Si supponga di voler creare un nuovo evento, chiamato negozio.ordine, distribuito ogni volta che un ordine viene creato dentro l'applicazione. Per mantenere le cose organizzate, iniziamo a creare una classe StoreEvents all'interno dell'applicazione, che serve a definire e documentare il proprio evento: Listing - namespace Acme\StoreBundle;. generated on August, 0 Chapter : Il componente EventDispatcher
155 0 final class StoreEvents /** * L'evento negozio.ordine è lanciato ogni volta che un ordine viene creato * nel sistema. * * L'ascoltatore dell'evento riceve un'istanza di * Acme\StoreBundle\Event\FilterOrderEvent. * string */ const STORE_ORDER = 'negozio.ordine'; Si noti che la class in realtà non fa nulla. Lo scopo della classe StoreEvents è solo quello di essere un posto in cui le informazioni sugli eventi comuni possano essere centralizzate. Si noti che anche che una classe speciale FilterOrderEvent sarà passata a ogni ascoltatore di questo evento. Creare un oggetto evento Più avanti, quando si distribuirà questo nuovo evento, si creerà un'istanza di Event e la si passerà al distributore. Il distributore quindi passa questa stessa istanza a ciascuno degli ascoltatori dell'evento. Se non si ha bisogno di passare informazioni agli ascoltatori, si può usare la classe predefinita Symfony\Component\EventDispatcher\Event. Tuttavia, la maggior parte delle volte, si avrà bisogno di passare informazioni sull'evento a ogni ascoltatore. Per poterlo fare, si creerà una nuova classe, che estende Symfony\Component\EventDispatcher\Event. In questo esempio, ogni ascoltatore avrà bisogno di accedere a un qualche oggetto Order. Creare una classe Event che lo renda possibile: Listing - 0 namespace Acme\StoreBundle\Event; use Symfony\Component\EventDispatcher\Event; use Acme\StoreBundle\Order; class FilterOrderEvent extends Event protected $order; public function construct(order $order) $this->order = $order; public function getorder() return $this->order; Ogni ascoltatore ora ha accesso all'oggetto Order, tramite il metodo getorder. Distribuire l'evento Il metodo dispatch() 0 notifica a tutti gli ascoltatori l'evento dato. Accetta due parametri: il nome dell'evento da distribuire e l'istanza di Event da passare a ogni ascoltatore di tale evento: 0. generated on August, 0 Chapter : Il componente EventDispatcher
156 Listing - 0 use Acme\StoreBundle\StoreEvents; use Acme\StoreBundle\Order; use Acme\StoreBundle\Event\FilterOrderEvent; // l'ordine viene in qualche modo creato o recuperato $order = new Order(); // creare FilterOrderEvent e distribuirlo $event = new FilterOrderEvent($order); $dispatcher->dispatch(storeevents::store_order, $event); Si noti che l'oggetto speciale FilterOrderEvent è creato e passato al metodo dispatch. Ora ogni ascoltatore dell'evento negozio.ordino riceverà FilterOrderEvent e avrà accesso all'oggetto Order, tramite il metodo getorder: Listing -0 // una qualche classe ascoltatore che è stata registrata per onstoreorder use Acme\StoreBundle\Event\FilterOrderEvent; public function onstoreorder(filterorderevent $event) $order = $event->getorder(); // fare qualcosa con l'ordine Usare i sottoscrittori Il modo più comune per ascoltare un evento è registrare un ascoltatore con il distributore. Questo ascoltatore può ascoltare uno o più eventi e viene notificato ogni volta che tali eventi sono distribuiti. Un altro modo per ascoltare gli eventi è tramite un sottoscrittore. Un sottoscrittore di eventi è una classe PHP che è in grado di dire al distributore esattamente quale evento dovrebbe sottoscrivere. Implementa l'interfaccia EventSubscriberInterface, che richiede un unico metodo statico, chiamato getsubscribedevents. Si consideri il seguente esempio di un sottoscrittore, che sottoscrive gli eventi kernel.response e negozio.ordine: Listing - 0 namespace Acme\StoreBundle\Event; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; class StoreSubscriber implements EventSubscriberInterface public static function getsubscribedevents() return array( 'kernel.response' => array( array('onkernelresponsepre', 0), array('onkernelresponsemid', ), array('onkernelresponsepost', 0), ), 'negozio.ordine' => array('onstoreorder', 0), );. generated on August, 0 Chapter : Il componente EventDispatcher
157 0 0 public function onkernelresponsepre(filterresponseevent $event) public function onkernelresponsemid(filterresponseevent $event) public function onkernelresponsepost(filterresponseevent $event) public function onstoreorder(filterorderevent $event) È molto simile a una classe ascoltatore, tranne che la classe stessa può dire al distributore quali eventi dovrebbe ascoltare. Per registrare un sottoscrittore con il distributore, usare il metodo addsubscriber() Listing - use Acme\StoreBundle\Event\StoreSubscriber; $subscriber = new StoreSubscriber(); $dispatcher->addsubscriber($subscriber); Il distributore registrerà automaticamente il sottoscrittore per ciascun evento restituito dal metodo getsubscribedevents. Questo metodo restituisce un array indicizzata per nomi di eventi e i cui valori sono o i nomi dei metodi da chiamare o array composti dal nome del metodo e da una priorità. L'esempio precedente mostra come registrare diversi metodi ascoltatori per lo stesso evento in un sottoscrittore e mostra anche come passare una priorità a ciascun metodo ascoltatore. Più è alta la priorità, prima sarà chiamato il metodo. Nell'esempio precedente, quando viene lanciato l'evento kernel.response, i metodi onkernelresponsepre, onkernelresponsemid e onkernelresponsepost sono richiamati in questo ordine. Bloccare il flusso e la propagazione degli eventi In alcuni casi, potrebbe aver senso che un ascoltatore prevenga il richiamo di qualsiasi altro ascoltatore. In altre parole, l'ascoltatore deve poter essere in grado di dire al distributore di bloccare ogni propagazione dell'evento a futuri ascoltatori (cioè di non notificare più altri ascoltatori). Lo si può fare da dentro un ascoltatore, tramite il metodo stoppropagation() : Listing - use Acme\StoreBundle\Event\FilterOrderEvent; public function onstoreorder(filterorderevent $event). generated on August, 0 Chapter : Il componente EventDispatcher
158 $event->stoppropagation(); Ora, tutti gli ascoltatori di negozio.ordine che non sono ancora stati richiamati non saranno richiamati. Si può individuare se un evento è stato fermato, usando il metodo ispropagationstopped(), che restituisce un booleano: Listing - $dispatcher->dispatch('foo.event', $event); if ($event->ispropagationstopped()) Eventi e ascolatori consapevoli del distributore New in version.: Da Symfony. il nome dell'evento corrente ed EventDispatcher stesso sono passati agli ascoltatori come parametri aggiuntivi. EventDispatcher inietta sempre l'evento distribuito, il nome dell'evento e un riferimento a sé stesso agli ascoltatori. Questo può portare ad applicazioni avanzate per EventDispatcher, incluse la possibilità per gli ascoltatori di distribuire altri eventi, il concatenamento degli eventi o anche il caricamento pigro di più ascoltatori nell'oggetto distributore. Ecco degli esempi: Caricamento pigro degli ascoltatori: Listing use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Acme\StoreBundle\Event\StoreSubscriber; class Foo private $started = false; public function mylazylistener(event $event, $eventname, EventDispatcherInterface $dispatcher) if (false === $this->started) $subscriber = new StoreSubscriber(); $dispatcher->addsubscriber($subscriber); $this->started = true; eccetera Distribuzione di altri eventi da dentro un ascoltatore: Listing - use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Foo public function myfoolistener(event $event, $eventname, EventDispatcherInterface. generated on August, 0 Chapter : Il componente EventDispatcher
159 0 $dispatcher) $dispatcher->dispatch('log', $event); eccetera Questo è sufficiente per la maggior parte dei casi, ma, se si ha un'applicazione con istanze multiple di EventDispatcher, potrebbe essere necessario iniettare specificatamente un'istanza nota di EventDispatcher nei propri ascoltatori. Questo è possibile tramite l'utilizzo dell'iniezione per costruttore o per setter, come segue: Iniezione per costruttore: Listing - 0 use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Foo protected $dispatcher = null; public function construct(eventdispatcherinterface $dispatcher) $this->dispatcher = $dispatcher; Iniezione tramite setter: Listing - 0 use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Foo protected $dispatcher = null; public function seteventdispatcher(eventdispatcherinterface $dispatcher) $this->dispatcher = $dispatcher; La scelta tra i due è una questione di gusti. Molti preferiscono l'iniezione per costruttore, perché l'oggetto in questo modo viene inizializzato durante la costruzione. Ma quando si ha una lunga lista di dipendenze, l'utilizzo dell'iniezione per setter può essere l'unico modo, specialmente per dipendenze opzionali. Scorciatoie del distributore Il metodo EventDispatcher::dispatch restituisce sempre un oggetto Event. Questo consente diverse scorciatoie. Per esempio, se non si ha bisogno di un oggetto evento personalizzato, ci si può appoggiare semplicemente su un oggetto Event. Non occorre nemmeno passarlo al distributore, perché ne sarà creato uno per impostazione predefinita, a meno che non venga passato specificatamente: Listing generated on August, 0 Chapter : Il componente EventDispatcher
160 $dispatcher->dispatch('foo.event'); Inoltre, EventDispatcher restituisce sempre quale oggetto evento è stato distribuito, cioè o l'evento passato o l'evento creato internamente dal distributore. Questo consente utili scorciatoie: Listing -0 if (!$dispatcher->dispatch('foo.event')->ispropagationstopped()) Oppure: Listing - $barevent = new BarEvent(); $bar = $dispatcher->dispatch('bar.event', $barevent)->getbar(); Oppure: Listing - $bar = $dispatcher->dispatch('bar.event', new BarEvent())->getBar(); e così via... Introspezione del nome dell'evento Poiché EventDispatcher conosce già il nome dell'evento al momento della distribuzione, il nome dell'evento è iniettato anche negli oggetti Event, quindi è disponibile agli ascoltatori dell'evento, tramite il metodo getname(). Il nome dell'evento (come ogni altro dato in un oggetto evento personalizzato) può essere usato come parte della logica di processamento dell'ascoltatore: Listing - use Symfony\Component\EventDispatcher\Event; class Foo public function myeventlistener(event $event) echo $event->getname(); Altri distributori Oltre a EventDispatcher, usato comunemente, il componente dispone di altri due distributori: Il distributore consapevole del contenitore L'Event Dispatcher Immutable. generated on August, 0 Chapter : Il componente EventDispatcher 0
161 Chapter Il distributore consapevole del contenitore Introduzione La classe ContainerAwareEventDispatcher è una speciale implementazione di distributore di eventi, accoppiata con il contenitore di servizi, che fa parte del component DependencyInjection. Questo consente di specificare i servizi come ascoltatori di eventi, rendendo EventDispatcher molto potente. Si servizi sono caricati in modo pigro, il che vuol dire che i servizi allegati come ascoltatori saranno creato solo se viene distribuito un evento che richieda tali ascoltatori. Preparazione La preparazione è molto semplice, basta iniettare un ContainerInterface in ContainerAwareEventDispatcher : Listing - use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; $container = new ContainerBuilder(); $dispatcher = new ContainerAwareEventDispatcher($container); Aggiungere ascoltatori Il distributore di eventi consapevole del contenitore può caricare direttamente servizi specifici, oppure servizi che implementino EventSubscriberInterface generated on August, 0 Chapter : Il distributore consapevole del contenitore
162 Gli esempi seguenti presumo che il DIC sia stato caricato con i servizi che vengono menzionati. I servizi devono essere segnati come pubblici nel DIC. Aggiungere servizi Per collegare definizioni di servizi esistenti, usare il metodo addlistenerservice(), dove $callback è un array array($idservizio, $nomemetodo): Listing - $dispatcher->addlistenerservice($eventname, array('pippo', 'loglistener')); Aggiungere servizi sottoscrittori Si possono aggiungere degli EventSubscribers, usando il metodo addsubscriberservice(), dove il primo parametro è l'id del servizio sottoscrittore e il secondo parametro è il nome della classe del servizio (che deve implementare EventSubscriberInterface ), come segue: Listing - $dispatcher->addsubscriberservice( 'kernel.store_subscriber', 'StoreSubscriber' ); EventSubscriberInterface sarà esattamente come ci si può aspettare: Listing use Symfony\Component\EventDispatcher\EventSubscriberInterface; class StoreSubscriber implements EventSubscriberInterface public static function getsubscribedevents() return array( 'kernel.response' => array( array('onkernelresponsepre', 0), array('onkernelresponsepost', 0), ), 'store.order' => array('onstoreorder', 0), ); public function onkernelresponsepre(filterresponseevent $event) public function onkernelresponsepost(filterresponseevent $event) generated on August, 0 Chapter : Il distributore consapevole del contenitore
163 0 public function onstoreorder(filterorderevent $event) generated on August, 0 Chapter : Il distributore consapevole del contenitore
164 Chapter Oggetto evento generico La classe base Event fornita dal componente Event Dispatcher è deliberatamente breve, per consentire la creazione di oggetti evento con API specifiche, usando l'ereditarietà. Questo consente un codice elegante e leggibile, anche in applicazioni complesse. La classe GenericEvent è disponibile per comodità per chi volesse usare un solo oggetto evento in tutta la propria applicazione. È adatta alla maggior parte degli scopi, senza modifiche, perché segue il pattern observer standard, in cui gli oggetti evento incapsulano il soggetto ("subject") di un evento, ma anche alcuni parametri in più. GenericEvent ha una semplice API, in aggiunta alla classe base Event construct() : il costruttore accetta il soggetto dell'evento e qualsiasi parametro; getsubject() : restituisce il soggetto; setargument() : imposta un parametro per chiave; setarguments() : imposta un array di parametri; getargument() : restituisce un parametro per chiave; getarguments() 0 : restituisce un array di parametri; hasargument() : restituisce true se il parametro esiste; GenericEvent implementa anche ArrayAccess sui parametri dell'evento, il che lo rende molto utile per passare parametri ulteriori, che riguardino il soggetto dell'evento. Gli esempi seguenti mostrano dei casi d'uso, che danno un'idea generale della flessibilità. Gli esempi presumono che gli ascoltatori siano stati aggiunti al distributore di eventi. Passare semplicemente un soggetto: construct() generated on August, 0 Chapter : Oggetto evento generico
165 Listing - 0 use Symfony\Component\EventDispatcher\GenericEvent; $event = GenericEvent($subject); $dispatcher->dispatch('pippo', $event); class PippoListener public function handler(genericevent $event) if ($event->getsubject() instanceof Pippo) Passare e processare parametri usando l'api ArrayAccess per accedere ai parametri dell'evento: Listing use Symfony\Component\EventDispatcher\GenericEvent; $event = new GenericEvent( $subject, array('type' => 'pippo', 'counter' => 0) ); $dispatcher->dispatch('pippo', $event); echo $event['counter']; class PippoListener public function handler(genericevent $event) if (isset($event['type']) && $event['type'] === 'pippo') fare qualcosa $event['counter']++; Filtrare i dati: Listing - 0 use Symfony\Component\EventDispatcher\GenericEvent; $evento = new GenericEvent($subject, array('data' => 'pippo')); $dispatcher->dispatch('pippo', $evento); echo $event['data']; class PippoListener public function filter(genericevent $evento) $event['data'] = strtolower($evento['data']);. generated on August, 0 Chapter : Oggetto evento generico
166 Chapter L'Event Dispatcher Immutable ImmutableEventDispatcher è un distributore di eventi bloccato o congelato. Il distributore non può registrare nuovi ascoltatori o sottoscrittori. ImmutableEventDispatcher accetta un altro distributore di eventi, con tutti gli ascoltatori e i sottoscrittore. Il distributore immutabile è solo un proxy di tale distributore originale. Per poterlo usare, creare dapprima un distributore normale (EventDispatcher o ContainerAwareEventDispatcher) e registrare degli ascoltatori o dei sottoscrittori: Listing - use Symfony\Component\EventDispatcher\EventDispatcher; $dispatcher = new EventDispatcher(); $dispatcher->addlistener('pippo.azione', function ($event) ); Quindi, iniettarlo in un ImmutableEventDispatcher: Listing - use Symfony\Component\EventDispatcher\ImmutableEventDispatcher; $immutabledispatcher = new ImmutableEventDispatcher($dispatcher); Si dovrà usare tale nuovo distributore nel proprio progetto. Se si prova a eseguire uno dei metodi che modificano il distributore (p.e. addlistener), verrà lanciata una BadMethodCallException.. generated on August, 0 Chapter : L'Event Dispatcher Immutable
167 Chapter Distributore di eventi tracciabile New in version.: La classe TraceableEventDispatcher è stata spostata nel componente EventDispatcher in Symfony.. In precedenza, si trovava nel componente HttpKernel. TraceableEventDispatcher è un distributore di eventi che avvolge ogni altro distributore di eventi e può quindi essere usato per determinare quale ascoltatori di eventi siano stati richiamati dal distributore. Passare il distributore di eventi da avvolgere e un'istanza di Stopwatch al suo costruttore: Listing - 0 use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; use Symfony\Component\Stopwatch\Stopwatch; // distributore di eventi su cui fare debug $eventdispatcher =...; $traceableeventdispatcher = new TraceableEventDispatcher( $eventdispatcher, new Stopwatch() ); Si può quindi usare TraceableEventDispatcher come qualsiasi altro distributore di eventi, per registrare ascoltatori di eventi e distribuire eventi: Listing - 0 // registrare un ascoltatore di eventi $eventlistener =...; $priority =...; $traceableeventdispatcher->addlistener('nome-evento, $eventlistener, $priority); // distribuire un evento $event =...; $traceableeventdispatcher->dispatch('nome-evento', $event);. generated on August, 0 Chapter : Distributore di eventi tracciabile
168 Dopo che un'applicazione è stata processata, si può usare il metodo getcalledlisteners() per recuperare un array di ascoltatori di eventi che sono stati richiamati nell'applicazione. In modo simile il metodo getnotcalledlisteners() restituisce un array di ascoltatori di eventi che non sono stati chiamati: Listing - $calledlisteners = $traceableeventdispatcher->getcalledlisteners(); $notcalledlisteners = $traceableeventdispatcher->getnotcalledlisteners();. TraceableEventDispatcherInterface.html#method_getCalledListeners. TraceableEventDispatcherInterface.html#method_getNotCalledListeners generated on August, 0 Chapter : Distributore di eventi tracciabile
169 Chapter Il componente ExpressionLanguage Il componente ExpressionLanguage fornisce un motore che compila e valuta espressioni. Un'espressione è una singola riga che restituisce un valore (soprattutto, ma non sempre, un booleano). New in version.: Il componente ExpressionLanguage è stato introdotto in Symfony.. Installazione Si può installare il componente in due modi: Installarlo tramite Composer (symfony/expression-language su Packagist ); Usare il repository ufficiale Git ( ). In che modo il linguaggio delle espressioni può essere utile? Lo scopo del componente è consentire agli utenti di usare espressioni all'interno della configurazione, per logiche più complesse. Per esempio, il framework Symfony usa espressioni nella sicurezza, per le regole di validazione e per la corrispondenza di rotte. Oltre a usare il componente nel framework, il componente ExpressionLanguage è un perfetto candidato per la fondazione di un motore di regole di business. L'idea è quella di lasciare al webmaster di un sito la possibilità di configurare le cose in modo dinamico, usando PHP e senza introdurre problemi di sicurezza: Listing - # Ottieni il prezzo speciale se user.getgroup() in ['good_customers', 'collaborator']. generated on August, 0 Chapter : Il componente ExpressionLanguage
170 # Promuovi l'articolo in homepage quando article.commentcount > 00 and article.category not in ["misc"] # Invia un avviso quando product.stock < Si possono vedere le espressioni come una sanbox PHP molto ristretta e immune a intrusioni esterne, dovendo dichiarare esplicitamente quali variabili sono disponibili in un'espressione. Uso Il componente ExpressionLanguage può compilare e valutare espressioni. Le espressioni sono righe che spesso restituiscono un booleano, che può essere usato nel codice che esegue l'espressione in un costrutto if. Un semplice esempio di espressione è +. Si possono usare espressioni più complesse, come unarray[].unmetodo('pluto'). Il componente fornisce due modi di lavorare con le espressioni: valuazione: l'espressione viene valutata senza essere compilata in PHP; compilazone: l'espressione viene compilata in PHP, in modo da poter essere messa in cache e valutata. La classe principale del componente è ExpressionLanguage : Listing - use Symfony\Component\ExpressionLanguage\ExpressionLanguage; $language = new ExpressionLanguage(); echo $language->evaluate(' + '); // mostra echo $language->compile(' + '); // mostra ( + ) Sintassi delle espressioni Vedere Sintassi di Expression per conoscere la sintassi del componente ExpressionLanguage. Passare variabili Si possono anche passare variabili dentro a un'espressione, le quali possono essere di qualsiasi tipo valido in PHP (inclusi oggetti): Listing - use Symfony\Component\ExpressionLanguage\ExpressionLanguage; $language = new ExpressionLanguage(); class Apple public $variety;. generated on August, 0 Chapter : Il componente ExpressionLanguage 0
171 0 $apple = new Apple(); $apple->variety = 'Honeycrisp'; echo $language->evaluate( 'fruit.variety', array( 'fruit' => $apple, ) ); Questo codice mostrerà "Honeycrisp". Per maggiori informazioni, vedere Sintassi di Expression, in particolare Lavorare con gli oggetti e Lavorare con gli array. Cache Il componente fornisce varie strategie di cache, si può approfondire in Cache di espressioni analizzate. generated on August, 0 Chapter : Il componente ExpressionLanguage
172 Chapter Sintassi di Expression Il componente ExpressionLanguage usa una sintassi specifica, basata sulla sintassi delle espressioni di Twig. In questo documento si potranno trovare tutte le sintassi supportate. Letterali supportati Il componente supporta: stringhe - con virgolette singole e doppie (p.e. 'ciao') numeri - p.e. 0 array - con notazione tipo JSON (p.e. [, ]) hash - con notazione tipo JSON (p.e. pippo: 'pluto' ) booleani - true e false nullo - null Lavorare con gli oggetti Quando si passano oggetti in un'espressione, si possono usare varie sintassi per accedere a proprietà e richiamare metodi. Accedere a proprietà pubbliche Si può accedere a proprietà pubbliche degli oggetti usando la sintassi., similmente a JavaScript: Listing - class Apple public $variety; $apple = new Apple(); $apple->variety = 'Honeycrisp'; generated on August, 0 Chapter : Sintassi di Expression
173 0 echo $language->evaluate( 'fruit.variety', array( 'fruit' => $apple, ) ); Mostrerà Honeycrisp. Richiamare metodi Si può usare la sintassi. anche per richiamare metodi dell'oggetto, similmente a JavaScript: Listing class Robot public function sayhi($times) $greetings = array(); for ($i = 0; $i < $times; $i++) $greetings[] = 'Ciao'; return implode(' ', $greetings).'!'; $robot = new Robot(); echo $language->evaluate( 'robot.sayhi()', array( 'robot' => $robot, ) ); Mostrerà Ciao Ciao Ciao!. Lavorare con le funzioni Si possono anche usare funzioni registrate nell'espressione, usando la stessa sintassi di PHP e JavaScript. Il componente ExpressionLanguage dispone già di una funzione: constant(), che restituisce il valore di una costante PHP: Listing - define('utente_db', 'root'); echo $language->evaluate( 'constant("utente_db")' ); Mostrerà root. generated on August, 0 Chapter : Sintassi di Expression
174 Le sapere come registrare funzioni da usare in un'espressione, vedere "Estendere ExpressionLanguage". Lavorare con gli array Se si passa un array in un'espressione, usare la sintassi [] per accedere all'array, similmente a JavaScript: Listing - $data = array('vita' => 0, 'universo' => 0, 'tutto_quanto' => ); echo $language->evaluate( 'data["vita"] + data["universo"] + data["tutto_quanto"]', array( 'data' => $data, ) ); Mostrerà. Operatori supportati Il componente disponde di vari operatori: Operatori aritmetici + (addizione) - (sottrazione) * (moltiplicazione) / (divisione) % (modulo) ** (potenza) Per esempio: Listing - echo $language->evaluate( 'vita + universo + tutto_quanto', array( 'vita' => 0, 'universe' => 0, 'tutto quanto' =>, ) ); Mostrerà. Operatori di bit & (and) (or) ^ (xor) generated on August, 0 Chapter : Sintassi di Expression
175 Operatori di confronto == (uguale) === (identico)!= (diverso)!== (non identico) < (minore) > (maggiore) <= (minore o uguale) >= (maggiore o uguale) matches (espressione regolare) Per verificare che una stringa non soddisfi un'espressione regolare, usare l'operatore logico not in combinazione con l'operatore matches: Listing - $language->evaluate('not ("pippo" matches "/pluto/")'); // restituisce true Si devono usare le parentesi, perché l'operatore unario not ha precedenza sull'operatore binario matches. Esempi: Listing - 0 $ret = $language->evaluate( 'vita == tutto_quanto', array( 'vita' => 0, 'universe' => 0, 'tutto quanto' =>, ) ); $ret = $language->evaluate( 'vita > tutto_quanto', array( 'vita' => 0, 'universe' => 0, 'tutto quanto' =>, ) ); Entrambe le variabili saranno impostate a false. Operatori logici not o! and o && or o Per esempio: Listing - $ret = $language->evaluate( 'vita < universo or vita < tutto_quanto', array( 'vita' => 0, generated on August, 0 Chapter : Sintassi di Expression
176 ); ) 'universe' => 0, 'tutto quanto' =>, La variabile $ret sarà impostata a true. Operatori di stringhe ~ (concatenazione) Per esempio: Listing - echo $language->evaluate( 'nome~" "~cognome', array( 'nome' => 'Arthur', 'cognome' => 'Dent', ) ); Mostrerà Arthur Dent. Operatori di array in (contiene) not in (non contiene) For example: Listing -0 0 class User public $group; $user = new User(); $user->group = 'risorse_umane'; $ingroup = $language->evaluate( 'user.group in ["risorse_umane", "marketing"]', array( 'user' => $user ) ); $ingroup sarà valutata a true. Operatori numerici.. (gamma) Per esempio: Listing - class User generated on August, 0 Chapter : Sintassi di Expression
177 0 public $age; $user = new User(); $user->age = ; $language->evaluate( 'user.age in..', array( 'user' => $user, ) ); Sarà valutata a true, perché user.age è compreso tra e. Operatori ternari pippo? 'sì' : 'no' pippo?: 'no' (uguale a pippo? pippo : 'no') pippo? 'sì' (uguale a pippo? 'sì' : '') generated on August, 0 Chapter : Sintassi di Expression
178 Chapter 0 Estendere ExpressionLanguage Si può estendere ExpressionLanguage, aggiungendo funzioni. Per esempio, nel framework Symfony, la sicurezza ha funzioni personalizzate per verificare il ruolo dell'utente. Se si vuole imparare come usare funzioni in un'espressione, leggere "Lavorare con le funzioni". Registrare funzioni Le funzioni sono registrate su una specifica istanza di ExpressionLanguage. Questo vuole dire che le funzioni possono essere usate in qualsiasi espressione eseguita da quella istanza. Per registrare una funzione, usare register(). Questo metodo ha tre parametri: name - il nome della funzione in un'espressione; compiler - una funzione eseguita quando si compila un'espressione usando la funzione; evaluator - una funzione eseguita quando l'espressione viene valutata. Listing 0-0 use Symfony\Component\ExpressionLanguage\ExpressionLanguage; $language = new ExpressionLanguage(); $language->register('lowercase', function ($str) return sprintf('(is_string(%$s)? strtolower(%$s) : %$s)', $str);, function ($arguments, $str) if (!is_string($str)) return $str; ); return strtolower($str);. generated on August, 0 Chapter 0: Estendere ExpressionLanguage
179 echo $language->evaluate('lowercase("hello")'); Questo mostrerà hello. Al compilatore e al valutatore viene passata come primo parametro una variabile arguments, che è uguale al secondo parametro per evaluate() o compile() (p.e. i "valori" quando si valuta o i "nomi" se si compila). Creare una nuova classe ExpressionLanguage Quando si usa la classe ExpressionLanguage in una libreria, si raccomanda di creare una nuova classe ExpressionLanguage e di registrarvi all'interno le funzioni. Sovrascrivere registerfunctions per aggiungere funzioni: Listing namespace Acme\AwesomeLib\ExpressionLanguage; use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage; class ExpressionLanguage extends BaseExpressionLanguage protected function registerfunctions() parent::registerfunctions(); // non dimenticare di registrare anche funzioni del nucleo $this->register('lowercase', function ($str) return sprintf('(is_string(%$s)? strtolower(%$s) : %$s)', $str);, function ($arguments, $str) if (!is_string($str)) return $str; ); return strtolower($str); generated on August, 0 Chapter 0: Estendere ExpressionLanguage
180 Chapter Cache di espressioni analizzate Il componente ExpressionLanguage fornisce già un metodo compile(), per consentire di mettere in cache le espressioni in puro PHP. Ma, internamente, il componente già mette in cache le espressioni analizzate, quindi le espressioni duplicate possono essere compilate e valutate più rapidamente. Il flusso di lavoro I metodi evaluate() e compile() necessitano entrambi di fare alcune cose, prima di poter restituire valori. Per evaluate(), questo overhead è più grande. Entrambi i metodi necessitano di spezzettare e analizzare l'espressione. Lo fanno tramite il metodo parse(). Questo metodo restituisce una ParsedExpression. Ora, il metodo compile() restituisce semplicemente la conversione in stringa di tale oggetto. Il metodo evaluate() deve ciclare tra i "nodi" (i pezzi di un'esoressione salvata in ParsedExpression) e valutarli al volo. Per risparmiare tempo, ExpressionLanguage mette in cache ParsedExpression, in modo da saltare i passi di spezzettamento e analisi con espressioni duplicate. La cache è eseguita da un'istanza di ParserCacheInterface (che usa un ArrayParserCache ). Si può personalizzare il comportamento, creando una ParserCache personalizzata e iniettandola nell'oggetto, tramite costruttore: Listing - use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Acme\ExpressionLanguage\ParserCache\MyDatabaseParserCache; $cache = new MyDatabaseParserCache(...); $language = new ExpressionLanguage($cache); generated on August, 0 Chapter : Cache di espressioni analizzate 0
181 DoctrineBridge fornisce un'implementazione di ParserCache, che usa la libreria di cache di Doctrine, che fornisce cache per ogni sorta di strategia, come APC,filesystem e Memcached. Uso di espressioni analizzate e serializzate Sia evaluate() sia compile() possono gestire ParsedExpression e SerializedParsedExpression: Listing - // il metodo parse() restituisce una ParsedExpression $expression = $language->parse(' + ', array()); echo $language->evaluate($expression); // mostra Listing - use Symfony\Component\ExpressionLanguage\SerializedParsedExpression; $expression = new SerializedParsedExpression( serialize($language->parse(' + ', array())) ); echo $language->evaluate($expression); // mostra. generated on August, 0 Chapter : Cache di espressioni analizzate
182 Chapter Il componente Filesystem Il componente Filesystem fornisce utilità di base per il filesystem. New in version.: Il componente Filesystem è nuovo in Symfony.. In precedenza, la classe Filesystem si trovava nel componente HttpKernel. Installazione Si può installare il componente in due modi: Installarlo tramite Composer (symfony/filesystem su Packagist ). Usare il repository ufficiale su Git ( ); Utilizzo La classe Filesystem è l'unico punto finale per le operazioni su filesystem: Listing - 0 use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Exception\IOException; $fs = new Filesystem(); try $fs->mkdir('/tmp/random/dir/'. mt_rand()); catch (IOException $e) echo "Errore durante la creazione della cartella"; generated on August, 0 Chapter : Il componente Filesystem
183 New in version.: IOExceptionInterface e il suo metodo getpath sono nuovi in Symfony.. Prima della., occorreva catturare la classe IOException. I metodi mkdir(), exists(), touch(), remove(), chmod(), chown() e chgrp() 0 possono ricevere una stringa, un array o un oggetto che implementi Traversable come parametro. Mkdir mkdir() crea una cartella. Su filesystem di tipo posix, le cartelle sono create in modalità predefinita 0. Si può usare il secondo parametro per impostare la modalità: Listing - $fs->mkdir('/tmp/photos', 000); Si può passare un array o un oggetto Traversable come primo parametro. Exists exists() verifica la presenza di tutti i file o cartelle e restituisce false se un file manca: Listing - // questa cartella esiste, restituisce true $fs->exists('/tmp/photos'); // rabbit.jpg esiste, bottle.png non esiste, restituisce false $fs->exists(array('rabbit.jpg', 'bottle.png')); Si può passare un array o un oggetto Traversable come primo parametro. Copy copy() copia file. Se la destinazione esiste già, file file è copiato solo se la data di modifica del sorgente è precedente a quella della destinazione. Questo comportamento è modificabile tramite un terzo parametro booleano: Listing generated on August, 0 Chapter : Il componente Filesystem
184 // funziona solo se image-icc è stato modificato dopo image.jpg $fs->copy('image-icc.jpg', 'image.jpg'); // image.jpg sarà sovrascritto $fs->copy('image-icc.jpg', 'image.jpg', true); Touch touch() imposta l'ora di accesso e modifica di un file. Per impostazione predefinita, usa l'ora attuale. Si può impostare un'ora diversa con il secondo parametro. Il terzo parametro è l'ora di accesso: Listing - // imposta l'ora di accesso al timestamp attuale $fs->touch('file.txt'); // imposta l'ora di modifica a 0 secondi nel futuro $fs->touch('file.txt', time() + 0); // imposta l'ora di accessoa 0 secondi nel passato $fs->touch('file.txt', time(), time() - 0); Si può passare un array o un oggetto Traversable come primo parametro. Chown chown() è usato per cambiare il proprietario di un file. Il terzo parametro è un booleano per un'opzione ricorsiva: Listing - // imposta il proprietario del video lolcat a www-data $fs->chown('lolcat.mp', 'www-data'); // cambia il proprietario della cartella video ricorsivamente $fs->chown('/video', 'www-data', true); Si può passare un array o un oggetto Traversable 0 come primo parametro. Chgrp chgrp() è usato per cambiare il gruppo di un file. Il terzo parametro è un booleano per un'opzione ricorsiva: Listing - // imposta il gruppo del video lolcat a nginx $fs->chgrp('lolcat.mp', 'nginx'); generated on August, 0 Chapter : Il componente Filesystem
185 // cambia il gruppo della cartella video ricorsivamente $fs->chgrp('/video', 'nginx', true); Si può passare un array o un oggetto Traversable come primo parametro. Chmod chmod() è usato per modificare la modalità di un file. Il terzo parametro è un'opzione ricorsiva booleana: Listing - // imposta la modalità di video.ogg a 000 $fs->chmod('video.ogg', 000); // imposta ricorsivamente la modalità della cartella src $fs->chmod('src', 000, true); Si può passare un array o un oggetto Traversable come primo parametro. Remove remove() rimuove file, collegamenti simbolici, cartelle: Listing - $fs->remove(array('symlink', '/path/to/directory', 'activity.log')); Si può passare un array o un oggetto Traversable come primo parametro. Rename rename() rinomina file e cartelle: Listing -0 // rinomina un file $fs->rename('/tmp/processed_video.ogg', '/path/to/store/video_.ogg'); // rinomina una cartella $fs->rename('/tmp/files', '/path/to/store/files'); generated on August, 0 Chapter : Il componente Filesystem
186 symlink symlink() crea un collegamento simbolico dal sorgente alla destinazione. Se il filesystem non supporta i collegamenti simbolici, c'è un terzo parametro booleano: Listing - // crea un collegamento simbolico $fs->symlink('/percorso/della/sorgente', '/percorso/della/destinazione'); // duplica la cartella sorgente, se il filesystem // non supporta i collegamenti simbolici $fs->symlink('/percorso/della/sorgente', '/percorso/della/destinazione', true); makepathrelative makepathrelative() restituisce il percorso relativo di una cartella, data un'altra: Listing - // restituisce '../' $fs->makepathrelative( '/var/lib/symfony/src/symfony/', '/var/lib/symfony/src/symfony/component' ); // restituisce 'videos' $fs->makepathrelative('/tmp/videos', '/tmp') mirror mirror() 0 esegute il mirror di una cartella: Listing - $fs->mirror('/percorso/della/sorgente', '/percorso/della/destinazione'); isabsolutepath isabsolutepath() restiuisce true se il percorso dato è assoluto, false altrimenti: Listing - // restituisce true $fs->isabsolutepath('/tmp'); // restituisce true $fs->isabsolutepath('c:\\windows'); // restituisce false $fs->isabsolutepath('tmp'); // restituisce false $fs->isabsolutepath('../dir'); dumpfile New in version.: dumpfile è nuovo in Symfony generated on August, 0 Chapter : Il componente Filesystem
187 dumpfile() consente di esportare contenuti in un file. Lo fa in maniera atomica: scrive prima un file temporaneo e quindi lo sposta nella nuova posizione, in cui viene finalizzato. Questo vuol dire che l'utente vedrà sempre o il vecchio file completo o il nuovo file completo (ma mai un file parziale): Listing - $fs->dumpfile('file.txt', 'Ciao mondo'); Il file file.txt ora contiene Ciao mondo. Si può passare come terzo parametro una modalità di file. Gestione degli errori Quando si verifica un problema, viene sollevata un'eccezione che implementa ExceptionInterface o IOExceptionInterface. Viene sollevata una IOException se la creazione della cartella fallisce generated on August, 0 Chapter : Il componente Filesystem
188 Chapter LockHandler (TODO da tradurre...) generated on August, 0 Chapter : LockHandler
189 Chapter Il componente Finder Il componente Finder cerca file e cartelle tramite un'interfaccia intuitiva e "fluida". Installazione È possibile installare il componente in due modi: Installandolo tramite Composer (symfony/finder su Packagist ); Usando il repository ufficiale su Git ( ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Uso La classe Finder trova i file e/o le cartelle: Listing - use Symfony\Component\Finder\Finder; $finder = new Finder(); $finder->files()->in( DIR ); foreach ($finder as $file) // Stampa il percorso assoluto print $file->getrealpath()."\n"; generated on August, 0 Chapter : Il componente Finder
190 0 // Stampa il percorso relativo del file, omettendo il nome del file stesso print $file->getrelativepath()."\n"; // Stampa il percorso relativo del file print $file->getrelativepathname()."\n"; $file è un'istanza di SplFileInfo la quale estende SplFileInfo che mette a disposizione i metodi per poter lavorare con i percorsi relativi. Il precedente codice stampa, ricorsivamente, i nomi di tutti i file della cartella corrente. La classe Finder implementa il concetto di interfaccia fluida, perciò tutti i metodi restituiscono un'istanza di Finder. Un Finder è un'istanza di un Iterator PHP. Perciò, invece di dover iterare attraverso Finder con un ciclo foreach, è possibile convertirlo in un array, tramite il metodo iterator_to_array, oppure ottenere il numero di oggetti in esso contenuti, con iterator_count. Quando si cerca in posizioni diverse, passate al metodo in(), internamente viene creato un iteratore separato per ogni posizione. Questo vuol dire che si ottengono vari insiemi di risultati, aggregati in uno solo. Poiché iterator_to_array 0 usa le chiavi degli insiemi di risultati, durante la conversione in array, alcune chiavi potrebbero essere duplicate e i loro valori sovrascritti. Lo si può evitare, passando false come secondo parametro di iterator_to_array. Criteri Ci sono molti modi per filtrare e ordinare i risultati. Posizione La posizione è l'unico parametro obbligatorio. Indica al finder la cartella da utilizzare come base per la ricerca: Listing - $finder->in( DIR ); Per cercare in diverse posizioni, è possibile concatenare diverse chiamate a in() : Listing - $finder->files()->in( DIR )->in('/altraparte'); Si possono usare caratteri jolly nelle cartelle, per cercare uno schema: Listing - $finder->in('src/symfony/*/*/resources'); generated on August, 0 Chapter : Il componente Finder 0
191 Ogni schema deve risolvere almeno un percorso di cartella. È possibile escludere cartelle dalla ricerca tramite il metodo exclude() : Listing - $finder->in( DIR )->exclude('ruby'); New in version.: Il metodo ignoreunreadabledirs() è stato aggiunto in Symfony.. È anche possibile ignorare le cartelle che non si ha il permesso di leggere: Listing - $finder->ignoreunreadabledirs()->in( DIR ); Visto che Finder utilizza gli iteratori di PHP, è possibile passargli qualsiasi URL con protocollo supportato: Listing - $finder->in('ftp://example.com/pub/'); Funziona anche con flussi definiti dall'utente: Listing - 0 use Symfony\Component\Finder\Finder; $s = new \Zend_Service_Amazon_S($chiave, $segreto); $s->registerstreamwrapper("s"); $finder = new Finder(); $finder->name('photos*')->size('< 00K')->date('since hour ago'); foreach ($finder->in('s://bucket-name') as $file) fare qualcosa print $file->getfilename()."\n"; Per approfondire l'argomento su come creare flussi personalizzati, si legga la documentazione degli stream. File o cartelle Il comportamento predefinito di Finder è quello di restituire file e cartelle, ma grazie ai metodi files() e directories(), è possibile raffinare i risultati: Listing - $finder->files(); $finder->directories(); Per seguire i collegamenti, è possibile utilizzare il metodo followlinks(): Listing generated on August, 0 Chapter : Il componente Finder
192 $finder->files()->followlinks(); Normalmente l'iteratore ignorerà i file dei VCS più diffusi. È possibile modificare questo comportamento, grazie al metodo ignorevcs(): Listing - $finder->ignorevcs(false); Ordinamento È possibile ordinare i risultati per nome o per tipo (prima le cartelle e poi i file): Listing - $finder->sortbyname(); $finder->sortbytype(); Si noti che i metodi sort*, per poter funzionare, richiedono tutti gli elementi ricercati. In caso di iteratori molto grandi, l'ordinamento potrebbe risultare lento. È anche possibile definire algoritmi di ordinamento personalizzati, grazie al metodo sort(): Listing - $sort = function (\SplFileInfo $a, \SplFileInfo $b) return strcmp($a->getrealpath(), $b->getrealpath()); ; $finder->sort($sort); Nomi dei file È possibile eseguire filtri sui nomi dei file, utilizzando il metodo name() : Listing - $finder->files()->name('*.php'); Il metodo name() accetta, come parametri, glob, stringhe o espressioni regolari: Listing - $finder->files()->name('/\.php$/'); Il metodo notnames() viene invece usato per escludere i file che corrispondono allo schema: Listing - $finder->files()->notname('*.rb'); Contenuti dei file Si possono filtrare file per contenuto, con il metodo contains() 0 : Listing generated on August, 0 Chapter : Il componente Finder
193 $finder->files()->contains('lorem ipsum'); Il metodo contains() accetta stringhe o espressioni regolari: Listing - $finder->files()->contains('/lorem\s+ipsum$/i'); Il metodo notcontains() esclude file che contengono lo schema dato: Listing - $finder->files()->notcontains('dolor sit amet'); Percorso Si possono filtrare file e cartelle per percorso, con il metodo path() : Listing -0 $finder->path('una/cartella/particolare'); Su tutte le piattaforme, bisogna usare la barra (cioè /) come separatore di cartelle. Il metodo path() accetta stringhe o espressioni regolari: Listing - $finder->path('pippo/pluto'); $finder->path('/^pippo\/pluto/'); Internamente, le stringhe sono convertite in espressioni regolari, tramite escape delle barre e aggiunta di delimitatori: Listing - nomecartella ===> /nomecartella/ a/b/c ===> /a\/b\/c/ Il metodo notpath() esclude i file per percorso: Listing - $finder->notpath('altra/cartella'); Dimensione dei file Per filtrare i file in base alla dimensione, si usa il metodo size() : Listing - $finder->files()->size('<.k'); Si possono filtrare i file di dimensione compresa tra due valori, concatenando le chiamate: Listing - $finder->files()->size('>= K')->size('<= K'); È possibile utilizzare uno qualsiasi dei seguenti operatori di confronto: >, >=, <, <=, ==,!=. La dimensione può essere indicata usando l'indicazione in kilobyte (k, ki), megabyte (m, mi) o in gigabyte (g, gi). Gli indicatori che terminano con i utilizzano l'appropriata versione **n, in accordo allo standard IEC generated on August, 0 Chapter : Il componente Finder
194 Data dei file È possibile filtrare i file in base alla data dell'ultima modifica, con il metodo date() : Listing - $finder->date('since yesterday'); È possibile utilizzare uno qualsiasi dei seguenti operatori di confronto: >, >=, <, '<=', '=='. È anche possibile usare i sostantivi since o after come degli alias di > e until o before come alias di <. Il valore usato può essere una data qualsiasi tra quelle supportate dalla funzione strtotime. Profondità della ricerca Normalmente, Finder attraversa ricorsivamente tutte le cartelle. Per restringere la profondità dell'attraversamento, si usa il metodo depth() : Listing - $finder->depth('== 0'); $finder->depth('< '); Filtri personalizzati È possibile definire filtri personalizzati, grazie al metodo filter() : Listing - $filtro = function (\SplFileInfo $file) if (strlen($file) > 0) return false; ; $finder->files()->filter($filtro); Il metodo filter() prende una Closure come argomento. Per ogni file che corrisponde ai criteri, la Closure viene chiamata passandogli il file come un'istanza di SplFileInfo. Il file sarà escluso dal risultato della ricerca nel caso in cui la Closure restituisca false. Leggere il contenuto dei file restituiti Il contenuto dei file restituiti può essere letto con getcontents() 0 : Listing - use Symfony\Component\Finder\Finder; $finder = new Finder(); $finder->files()->in( DIR ); foreach ($finder as $file) $contents = $file->getcontents(); generated on August, 0 Chapter : Il componente Finder
195 0 generated on August, 0 Chapter : Il componente Finder
196 Chapter Il componente Form Il componente Form consente di creare, processare e riusare facilmente form HTML. Il componente Form è uno strumento che aiuta a risolvere il problema di consentire agli utenti finali di interagire con i dati e modificare i dati in un'applicazione. Sebbene, tradizionalmente, ciò viene fatto tramite form HTML, il componente si focalizza sul processamento dei dati da e verso il client e l'applicazione, sia che i dati vengano da un classico form, sia che vengano da un'api. Installazione Si può installare il componente in due modi: Installarlo tramite Composer (symfony/form su Packagist ). Usare il repository ufficiale Git ( ); Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Configurazione Se si lavora con il framework Symfony, il componente Form è già configurato. In questo caso, passare a Creazione di un semplice form. In Symfony, i form sono rappresentati da oggetti e tali oggetti sono costruiti usando un factory di form. Costruire un factory di form è semplice:. generated on August, 0 Chapter : Il componente Form
197 Listing - use Symfony\Component\Form\Forms; $formfactory = Forms::createFormFactory(); Questo factory può già essere usato per creare form di base, ma manca di supporto per alcune cose importanti: Gestione della richiesta: Supporto per gestione delle richieste e caricamento di file; Protezione da CSRF: Supporto per protezione contro attacchi di tipo Cross-Site-Request- Forgery (CSRF); Template: Integrazione con uno strato di template, che consenta di riutilizzare frammenti HTML usando un form; Traduzione: Supporto per la traduzione di messaggi di errore, label dei campi e altre stringhe; Validazione: Integrazione con una libreria di validazione, per generare messaggi di errore per i dati inseriti. Il componente Form si appoggia su altre librerie per risolvere questi problemi. La maggior parte delle volte si useranno i componenti Twig e HttpFoundation, Translation e Validator, ma si possono sostituire tutte queste librerie con altre a scelta. Le sezioni seguenti spiegano come usare queste librerie insieme al factory di form. Per un esempio funzionante, si veda Gestione della richiesta New in version.: Il metodo handlerequest() è stato introdotto in Symfony.. Per processare i dati di un form, occorre richiamare il metodo handlerequest() : Listing - $form->handlerequest(); Dietro le quinte, viene usato un oggetto NativeRequestHandler per leggere i dati dalle opportune variabili di PHP ($_POST o $_GET), in base al metodo HTTP configurato nel form (quello predefinito è POST). Se occorre maggiore controllo su come esattamente il form viene inviato o su quali dati vi siano passati, si può usare submit(). Per saperne di più, vedere il ricettario generated on August, 0 Chapter : Il componente Form
198 Integrazione con il componente HttpFoundation Per l'integrazione con HttpFoundation, aggiungere HttpFoundationExtension al factory di form: Listing - use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension; $formfactory = Forms::createFormFactoryBuilder() ->addextension(new HttpFoundationExtension()) ->getformfactory(); Ora, quando si processa un form, si può passare l'oggetto Request a handlerequest() : Listing - $form->handlerequest($request); Per maggiori informazioni sul componente HttpFoundation e su come installarlo, vedere Il componente HttpFoundation. Protezione da CSRF La protezione da attacchi CSRF è compresa nel componente Form, ma occorre abilitarla esplicitamente o rimpiazzarla con una soluzione personalizzata. Il codice seguente aggiunge la protezione da CSRF al factory di form: Listing - 0 use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Csrf\CsrfExtension; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\SessionCsrfProvider; use Symfony\Component\HttpFoundation\Session\Session; // generare in qualche modo una parola segreta $csrfsecret = '<generated token>'; // creare un oggetto sessione da HttpFoundation $session = new Session(); $csrfprovider = new SessionCsrfProvider($session, $csrfsecret); $formfactory = Forms::createFormFactoryBuilder() ->addextension(new CsrfExtension($csrfProvider)) ->getformfactory(); Per proteggere un'applicazione da attacchi CSRF, occorre definire una parola segreta. Generare una stringa casuale con almeno caratteri, inserirla nel codice appena visto e assicurarsi che nessuno, tranne il server web, possa accedervi. Internamente, l'estensione aggiungerà automaticamente a ogni form un campo nascosto (chiamato token), il cui valore è automaticamente generato e validato generated on August, 0 Chapter : Il componente Form
199 Se non si usa il componente HttpFoundation, usare DefaultCsrfProvider 0, che si basa sulla gestione nativa di PHP delle sessioni: Listing - use Symfony\Component\Form\Extension\Csrf\CsrfProvider\DefaultCsrfProvider; $csrfprovider = new DefaultCsrfProvider($csrfSecret); Template Twig Se si usa il componente Form per processare form HTML, occorrerà un modo per rendere facilmente i form come campi HTML (completi con valori, errori e label). Se si usa Twig come motore di template, il componente Form offre una ricca integrazione. Per usare tale integrazione, occorre TwigBridge, che integra Twig con vari componenti di Symfony. Usando Composer, si può installare la versione. più recente. aggiungendo la seguente riga al file composer.json: Listing - "require": "symfony/twig-bridge": "..*" L'integrazione TwigBridge fornisce varie funzioni Twig, che aiutano a rendere ciascun widget, label ed errore per ogni campo (insieme ad alcune altre cose). Per configurare l'integrazione, occorrerà accedere a Twig e aggiungere FormExtension : Listing use Symfony\Component\Form\Forms; use Symfony\Bridge\Twig\Extension\FormExtension; use Symfony\Bridge\Twig\Form\TwigRenderer; use Symfony\Bridge\Twig\Form\TwigRendererEngine; // il file Twig con tutti i tag per i form // questo file fa parte di TwigBridge $defaultformtheme = 'form_div_layout.html.twig'; $vendordir = realpath( DIR.'/../vendor'); // percorso di TwigBridge, che consente a Twig di trovare il file // form_div_layout.html.twig $vendortwigbridgedir = $vendordir. '/symfony/twig-bridge/symfony/bridge/twig'; // percorso degli altri template $viewsdir = realpath( DIR.'/../views'); $twig = new Twig_Environment(new Twig_Loader_Filesystem(array( $viewsdir, $vendortwigbridgedir.'/resources/views/form', ))); $formengine = new TwigRendererEngine(array($defaultFormTheme)); $formengine->setenvironment($twig); // aggiunge FormExtension a Twig generated on August, 0 Chapter : Il componente Form
200 0 $twig->addextension( new FormExtension(new TwigRenderer($formEngine, $csrfprovider)) ); // creare il factory, come al solito $formfactory = Forms::createFormFactoryBuilder() ->getformfactory(); I dettagli esatti della configurazione di Twig possono variare, ma lo scopo è sempre quello di aggiungere FormExtension a Twig, che dà accesso alle funzioni Twig functions per rendere i form. Per poterlo fare, occorre prima creare un TwigRendererEngine, in cui definire i propri form themes (cioè file o risorse che definiscono i tag HTML per i form). Per dettagli sulla resa dei form, vedere Personalizzare la resa dei form. Se si usa l'integrazione con Twig, leggere "Traduzione" più avanti, per i dettagli sui necessari filtri di traduzione. Traduzione Se si usa l'integrazione con Twig con uno dei file di temi di form predefiniti (come form_div_layout.html.twig), ci sono due filtri Twig (trans e transchoice), usati per tradurre label, errori, opzioni e altre stringhe. Per aggiungere questi filtri, si può usare TranslationExtension, che si integra con il componente Translation, oppure aggiungere i due filtri a mano, tramite un'estensione Twig. Per usare l'integrazione predefinita, assicurarsi che il progetto abbia i componenti Translation e Config installati. Se si usa Composer, si possono ottenere le versioni. più recenti di entrambi aggiungendo le seguenti righe al file composer.json: Listing - "require": "symfony/translation": "..*", "symfony/config": "..*" Aggiungere quindi TranslationExtension all'istanza di Twig_Environment: Listing -0 use Symfony\Component\Form\Forms; use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\Loader\XliffFileLoader; use Symfony\Bridge\Twig\Extension\TranslationExtension; // creare il Translator $translator = new Translator('en'); // caricare traduzioni in qualche modo generated on August, 0 Chapter : Il componente Form 00
201 0 0 $translator->addloader('xlf', new XliffFileLoader()); $translator->addresource( 'xlf', DIR.'/percorso/delle/traduzioni/messages.en.xlf', 'en' ); // aggiungere TranslationExtension (fornisce i filtri trans e transchoice) $twig->addextension(new TranslationExtension($translator)); $formfactory = Forms::createFormFactoryBuilder() ->getformfactory(); A seconda di come sono state caricate le traduzioni, si possono ora aggiungere chiavi stringa, come label di campi, e le loro traduzioni nei file di traduzione. Per maggiori dettagli sulle traduzioni, vedere Traduzioni. Validazione Il componente Form dispone di un'integrazione stretta (ma facoltativa) con il componente Validator di Symfony. Si può anche usare una soluzione diversa per la validazione. Basta prendere i dati inseriti nel form (che sono un array o un oggetto) e passarli al proprio sistema di validazione. Per usare l'integrazione con il componente Validator, assicurarsi innanzitutto di installarlo nell'applicazione. Se si usa Composer e si vogliono installare le versioni. più recenti, aggiungere a composer.json: Listing - "require": "symfony/validator": "..*" Chi non avesse familiarità con il componente Validator può approfondire su Validazione. Il componente Form dispone di una classe ValidatorExtension, che applica automaticamente la validazione ai dati. Gli errori sono quindi mappati sui rispettivi campi e resi. L'integrazione con il componente Validation sarà simile a questa: Listing - 0 use Symfony\Component\Form\Forms; use Symfony\Component\Form\Extension\Validator\ValidatorExtension; use Symfony\Component\Validator\Validation; $vendordir = realpath( DIR.'/../vendor'); $vendorformdir = $vendordir.'/symfony/form/symfony/component/form'; $vendorvalidatordir = $vendordir.'/symfony/validator/symfony/component/validator'; // creare il validatore (i dettagli possono variare) $validator = Validation::createValidator(); // ci sono traduzioni predefinite per i messaggi di errore principali $translator->addresource( 'xlf',. generated on August, 0 Chapter : Il componente Form 0
202 0 0 $vendorformdir.'/resources/translations/validators.en.xlf', 'en', 'validators' ); $translator->addresource( 'xlf', $vendorvalidatordir.'/resources/translations/validators.en.xlf', 'en', 'validators' ); $formfactory = Forms::createFormFactoryBuilder() ->addextension(new ValidatorExtension($validator)) ->getformfactory(); Per approfondire, vedere la sezione Validazione di form. Accesso al factory dei form L'applicazione ha bisogno di un unico factory di form, quello che andrebbe usato per creare tutti gli oggetti form nell'applicazione. Questo vuol dire che andrebbe creato in una parte centralizzata iniziale dell'applicazione e quindi acceduto ovunque ci sia bisogno di costruire un form. In questo documento, il factory di form è sempre una variabile locale, chiamata $formfactory. Il punto è che probabilmente si avrà la necessità di creare questo oggetto in un qualche modo "globale", per potervi accedere ovunque. Il modo esatto in cui si accede al factory di form dipende dallo sviluppatore. Se si usa un Contenitore di servizi, si dovrebbe aggiungere il factory di form al contenitore e recuperarlo all'occorrenza. Se l'applicazione usa variabili globali o statiche (di solito una cattiva idea), si può memorizzare l'oggetto in una classe statica o qualcosa del genere. Indipendentemente dall'architettura dell'applicazione, si ricordi che si dovrebbe avere solo un factory di form e che occorrerà accedervi in ogni parte dell'applicazione. Creazione di un semplice form Se si usa il framework Symfony, il factory di form è disponibile automaticamente come servizio, chiamato form.factory. Inoltre, la classe controller base ha un metodo createformbuilder(), che è una scorciatoia per recuperare il factory di form e richiamare createbuilder su di esso. La creazione di un form si esegue tramite un oggetto FormBuilder 0, in cui si costruiscono e configurano i vari campi. Il costruttore di form è creato dal factory di form. Listing - $form = $formfactory->createbuilder() ->add('task', 'text') ->add('duedate', 'date') ->getform(); generated on August, 0 Chapter : Il componente Form 0
203 echo $twig->render('new.html.twig', array( 'form' => $form->createview(), )); Come si può vedere, creare un form è come scrivere una ricettta: si richiama add per ogni nuovo campo da creare. Il primo parametro di add è il nome del campo, il secondo il "tipo" di campo. Il componente Form dispone di molti tipi già pronti. Una volta costruito il form, si può capire come renderlo e come processarne l'invio. Impostazione di valori predefiniti Se il form deve caricare alcuni valori predefiniti (o se si sta costruendo un form di modifica), basta passare i dati predefiniti durante la creazione del costruttore di form: Listing - $defaults = array( 'duedate' => new \DateTime('tomorrow'), ); $form = $formfactory->createbuilder('form', $defaults) ->add('task', 'text') ->add('duedate', 'date') ->getform(); In questo esempio, i dati predefiniti sono in un array, se invece si usa l'opzione data_class per legare i dati direttamente a oggetti, i dati predefiniti saranno un'istanza dell'oggetto specificato. Resa del form Una volta creato il form, il passo successivo è renderlo. Lo si può fare passando un oggetto "vista" del form a un template (si noti la chiamata a $form->createview() nel controllore visto sopra) e usando delle funzioni aiutanti: Listing - <form action="#" method="post" form_enctype(form) > form_widget(form) <input type="submit" /> </form> Ecco fatto! Richiamando form_widget(form), viene reso ogni campo del form, insieme a label ed eventuali messaggi di errore. Essendo facile, non è ancora molto flessibile. Di solito, si vuole rendere ogni generated on August, 0 Chapter : Il componente Form 0
204 campo del form singolarmente, in modo da poterne controllare l'aspetto. Si vedrà come farlo nella sezione "Rendere un form in un template". Cambiare metodo e azione del form New in version.: La possibilità di configurare metodo e azione del form è stata introdotta in Symfony.. Ogni form viene normalmente inviato allo stesso URI in cui è stato reso, con una richiesta HTTP POST. Si può modificare tale comportamento, usando le opzioni form-option-action e form-option-method (l'opzione method è usata anche da handlerequest(), per determinare se il form sia stato inviato): Listing - $formbuilder = $formfactory->createbuilder('form', null, array( 'action' => '/search', 'method' => 'GET', )); Gestione dell'invio di form Per gestire l'invio del form, usare il metodo handlerequest() : Listing use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RedirectResponse; $form = $formfactory->createbuilder() ->add('task', 'text') ->add('duedate', 'date') ->getform(); $request = Request::createFromGlobals(); $form->handlerequest($request); if ($form->isvalid()) $data = $form->getdata(); fare qualcosa, come salvare i dati $response = new RedirectResponse('/task/success'); $response->prepare($request); return $response->send(); In questo modo si definisce un flusso comune per i form, con tre diverse possibilità:. Nella richiesta GET iniziale (cioè quando l'utente apre la pagina), costruire e mostrare il form; Se la richiesta è POST, processare i dati inseriti (tramite handlerequest()). Quindi:. se il form non è valido, rendere nuovamente il form (che ora contiene errori). se il form è valido, eseguire delle azioni e redirigere. Per fortuna, non serve decidere se il form sia stato inviato o meno. Basta passare la richiesta al metodo handlerequest(). Quindi, il componente Form svolgerà tutto il lavoro necessario.. generated on August, 0 Chapter : Il componente Form 0
205 Validazione di form Il modo più facile di aggiungere validazione ai form è tramite l'opzione constraints, durante la costruzione di ogni campo: Listing - 0 use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\Type; $form = $formfactory->createbuilder() ->add('task', 'text', array( 'constraints' => new NotBlank(), )) ->add('duedate', 'date', array( 'constraints' => array( new NotBlank(), new Type('\DateTime'), ) )) ->getform(); Al bind del form, questi vincoli di validazione saranno automaticamente applicati e gli eventuali errori mostrati accanto ai rispettivi campi. Per un elenco di tutti i vincoli disponibili, vedere Riferimento per i vincoli di validazione. Accedere agli errori New in version.: Prima di Symfony., geterrors() restituiva un array di oggetti FormError. Il valore restituito è stato cambiato in un FormErrorIterator in Symfony.. New in version.: I parametri $deep e $flatten sono stati introdotti in Symfony.. Si può usare il metodo geterrors() per accedere alla lista degli errori. Ogni elemento è un oggetto FormError : Listing - 0 $form =...; // un array di oggetti FormError, ma solo di errori allegati a questo // livello del form (p.e. "errori globali") $errori = $form->geterrors(); // un array di oggetti FormError, ma solo di errori allegati al campo // "nome" $errori = $form['nome']->geterrors(); // un'istanza di FormErrorIterator in una struttura appiattita // usare getorigin() per determinare il form che ha causato l'errore $errors = $form->geterrors(true);. generated on August, 0 Chapter : Il componente Form 0
206 // un'istanza di FormErrorIterator che rappresenta tutti gli errori dell'intero form $errori = $form->geterrors(true, false); Nelle versioni precedenti di Symfony, geterrors() restituiva un array. Per usare gli errori nello stesso modo in Symfony. o successivi, si deve passarli alla funzione iterator_to_array di PHP: Listing -0 $erroricomearray = iterator_to_array($form->geterrors()); Questo può essere utile, per esempio, se si vuole usare una funzione array_ di PHP sugli errori del form.. generated on August, 0 Chapter : Il componente Form 0
207 Chapter Creare un indovino di tipi Il componente Form può indovinare il tipo e alcune opzioni di un campo, usando gli indovini di tipo. Il componente include già un indovino di tipi, che usa le asserzioni del componente Validation, ma si possono anche aggiungere degli indovini personalizzati. Indovini di tipi di form nei bridge Symfony fornisce anche alcuni indovini di tipi di form nei bridge: PropelTypeGuesser fornito dal Propel bridge; DoctrineOrmTypeGuesser fornito dal Doctrine bridge. Creare un indovino di tipi PHPDoc In questa sezione verrà costruito un indovino, che legga informazioni su un campo dal PHPDoc delle proprietà. Per iniziare, occorre creare una classe che implementi FormTypeGuesserInterface. Tale interfaccia richiede quattro metodi: guesstype() - cerca di indovinare il tipo di un campo; guessrequired() - cerca di indovinare il valore per l'opzione required ; guessmaxlength() - cerca di indovinare il valore per l'opzione max_length ; guesspattern() - cerca di indovinare il valore per l'opzione pattern. Bisogna dunque creare una classe con questi metodi. Saranno riempiti in un momento successivo. Listing generated on August, 0 Chapter : Creare un indovino di tipi 0
208 0 0 namespace Acme\Form; use Symfony\Component\Form\FormTypeGuesserInterface; class PhpdocTypeGuesser implements FormTypeGuesserInterface public function guesstype($class, $property) public function guessrequired($class, $property) public function guessmaxlength($class, $property) public function guesspattern($class, $property) Indovinare il tipo Quando si indovina un tipo, il metodo restituisce un'istanza di TypeGuess determinare che l'indovino di tipi non riesce a indovinare il tipo. Il costruttore di TypeGuess ha bisogno di tre opzioni: oppure niente, per Il nome del tipo (uno dei tipi di form); Opzioni aggiuntive (per esempio, quando il tipo è entity, si deve anche impostare l'opzione class). Se non viene indovinato alcun tipo, va impostato a un array vuoto; La fiducia sulla correttezza del tipo indovinato. Può essere una delle costanti della classe Guess : LOW_CONFIDENCE, MEDIUM_CONFIDENCE, HIGH_CONFIDENCE, VERY_HIGH_CONFIDENCE. Dopo che tutti gli indovini di tipo sono stati eseguiti, viene usato il tipo con fiducia maggiore. Con questo in mente, si può facilmente implementare il metodo guesstype di PHPDocTypeGuesser: Listing - 0 namespace Acme\Form; use Symfony\Component\Form\Guess\Guess; use Symfony\Component\Form\Guess\TypeGuess; class PhpdocTypeGuesser implements FormTypeGuesserInterface public function guesstype($class, $property) $annotations = $this->readphpdocannotations($class, $property); if (!isset($annotations['var'])) return; // non indovinare niente se non è disponibile. generated on August, 0 Chapter : Creare un indovino di tipi 0
209 // altrimenti, basa il tipo switch ($annotations['var']) case 'string': // c'è una fiducia alta che il tipo sia testo, se è stata trovata string return new TypeGuess('text', array(), Guess::HIGH_CONFIDENCE); case 'int': case 'integer': // gli interi possono essere l'id di un'entità o un checkbox (0 o ) return new TypeGuess('integer', array(), Guess::MEDIUM_CONFIDENCE); case 'float': case 'double': case 'real': return new TypeGuess('number', array(), Guess::MEDIUM_CONFIDENCE); case 'boolean': case 'bool': return new TypeGuess('checkbox', array(), Guess::HIGH_CONFIDENCE); default: // c'è una fiducia molto bassa che questo sia corretto return new TypeGuess('text', array(), Guess::LOW_CONFIDENCE); protected function readphpdocannotations($class, $property) $reflectionproperty = new \ReflectionProperty($class, $property); $phpdoc = $reflectionproperty->getdoccomment(); // analizza $phpdoc in un array come: // array('type' => 'string', 'since' => '.0') $phpdoctags =...; return $phpdoctags; Questo indovino di tipi ora può indovinare il tipo di campo per una proprietà, se questa dispone di PHPDoc. Indovinare le opzioni del campo I restanti tre metodi (guessmaxlength, guessrequired e guesspattern) restituiscono un'istanza di ValueGuess 0, con il valore dell'opzione. Questo costruttore ha due parametri: Il valore dell'opzione; La fiducia che il valore indovinato sia corretto (usando le costanti della classe Guess). Viene restituito null quando si ritiene di non poter impostare il valore dell'opzione generated on August, 0 Chapter : Creare un indovino di tipi 0
210 Occorre molta cautela con il metodo guesspattern. Quando il tipo è un float, non lo si può usare per determinare un valore minimo o massimo per il float (p.e. si vuole che un float sia maggiore di,. non è valido ma length(.) > length(), quindi lo schema avrebbe success). In questo caso, il valore va impostato a null con MEDIUM_CONFIDENCE. Registrare un indovino di tipi L'ultimo passo da eseguire è registrare l'indovino di tipi, usando addtypeguesser() o addtypeguessers() : Listing - use Symfony\Component\Form\Forms; use Acme\Form\PHPDocTypeGuesser; $formfactory = Forms::createFormFactoryBuilder() ->addtypeguesser(new PHPDocTypeGuesser()) ->getformfactory(); Se si usa il framework Symfony, occorre registrare l'indovino di tipi con il tag form.type_guesser. Per maggiori informazioni, vedere the tag reference generated on August, 0 Chapter : Creare un indovino di tipi 0
211 Chapter Eventi dei form Il componente Form fornisce un processo strutturato, che consente di personalizzare i form, facendo uso del componente EventDispatcher. Usando gli eventi dei form, si possono modificare informazioni o campi in vari punti del flusso: dal popolamento del form all'invio dei dati dalla richiesta. La registrazione di ascoltatori di eventi è molto facile, usando il componente Form. Per esempio, se si vuole registrare una funzione all'evento FormEvents::PRE_SUBMIT, il codice seguente consente di aggiungere un campo, a seconda dei valori della richiesta: Listing - 0 use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; $listener = function (FormEvent $event) ; $form = $formfactory->createbuilder() // aggiunge campi al form ->addeventlistener(formevents::pre_submit, $listener); generated on August, 0 Chapter : Eventi dei form
212 Il flusso del form Il flusso di invio del form generated on August, 0 Chapter : Eventi dei form
213 ) Pre-popolare il form (FormEvents::PRE_SET_DATA e FormEvents::POST_SET_DATA) Durante il pre-popolamento di un form, sono distribuiti due eventi, al richiamo del metodo Form::setData() : FormEvents::PRE_SET_DATA e FormEvents::POST_SET_DATA. A) L'evento FormEvents::PRE_SET_DATA L'evento FormEvents::PRE_SET_DATA è distribuito all'inizio del metodo Form::setData(). Può essere usato per:. generated on August, 0 Chapter : Eventi dei form
214 Modificare i dati forniti durante il pre-popolamento; Modificare un form a seconda dei dati di pre-popolamento (aggiunta o rimozione dinamica di campi). Tabella informativa sugli eventi dei form Tipo di dati Dati modello Dati normalizzati Dati vista Valore null null null Durante FormEvents::PRE_SET_DATA, Form::setData() è bloccato e lancerà un'eccezione, se usato. Se si vogliono modificare i dati, usare invece FormEvent::setData(). FormEvents::PRE_SET_DATA nel componente Form Il tipo di form collection si appoggia al sottoscrittore ResizeFormListener, ascoltando l'evento FormEvents::PRE_SET_DATA per poter riordinare i campi del form, in base ai dati pre-popolati dell'oggetto, rimuovendo e aggiungendo tutte le righe del form. B) L'evento FormEvents::POST_SET_DATA L'evento FormEvents::POST_SET_DATA è distribuito alla fine del metodo Form::setData(). Questo evento per lo più serve a leggere dati dopo aver pre-popolato il form. Tabella informativa sugli eventi dei form Tipo di dati Dati modello Dati normalizzati Dati vista Valore Dati del modello iniettati in setdata() Dati del modello trasformati con un trasformatore di modello Dati normalizzati trasformati con un trasformatore di vista FormEvents::POST_SET_DATA nel componente Form New in version.: L'estensione per raccogliere i dati è stata introdotta in Symfony.. La classe DataCollectorListener ascolta l'evento FormEvents::POST_SET_DATA, per poter raccogliere informazioni sui form dal modello denormalizzato e dai dati della vista generated on August, 0 Chapter : Eventi dei form
215 ) Inviare un form (FormEvents::PRE_SUBMIT, FormEvents::SUBMIT e FormEvents::POST_SUBMIT) generated on August, 0 Chapter : Eventi dei form
216 generated on August, 0 Chapter : Eventi dei form
217 Tre eventi sono distribuiti quando Form::handleRequest() o Form::submit() vengono richiamati: FormEvents::PRE_SUBMIT, FormEvents::SUBMIT, FormEvents::POST_SUBMIT. A) L'evento FormEvents::PRE_SUBMIT L'evento FormEvents::PRE_SUBMIT è distribuito all'inizio del metodo Form::submit(). Può essere usato per: Cambiare i dati dalla richiesta, prima di inviare i dati al form. Aggiungere i rimovere campi dal form, prima di inviare i dati al form. Tabella informativa sugli eventi dei form Tipo di dati Dati modello Dati normalizzati Dati vista Valore Come in FormEvents::POST_SET_DATA Come in FormEvents::POST_SET_DATA Come in FormEvents::POST_SET_DATA FormEvents::PRE_SUBMIT nel componente Form Il sottoscrittore TrimListener 0 ascolta l'evento FormEvents::PRE_SUBMIT, per poter applicare un trim ai dati della richiesta (per valori stringa). Il sottoscrittore CsrfValidationListener ascolta l'evento FormEvents::PRE_SUBMIT, per poter validare il token CSRF. B) L'evento FormEvents::SUBMIT L'evento FormEvents::SUBMIT è distribuito subito prima che il metodo Form::submit() ritrasformi i dati normalizzati in dati di modello e di vista. Può essere usato per cambiare dati dalla rappresentazione normalizzata dei dati. Tabella informativa sugli eventi dei form Tipo di dati Dati modello Dati normalizzati Dati vista Valore Come in FormEvents::POST_SET_DATA Dati ritrasformati dalla richiesta usando un trasformatore di vista Come in FormEvents::POST_SET_DATA A questo punto, non si possono aggiungere o rimuovere campi dal form generated on August, 0 Chapter : Eventi dei form
218 FormEvents::SUBMIT nel componente Form ResizeFormListener ascolta l'evento FormEvents::SUBMIT per poter rimuovere i campi che devono essere rimossi, se è stata abilitata la manipolazione di collezioni di form tramite allow_delete. C) L'evento FormEvents::POST_SUBMIT L'evento FormEvents::POST_SUBMIT è distribuito dopo Form::submit(), una volta che i dati di modello e vista sono stati denormalizzati. Può essere usato per recuperare dati dopo la denormalizzazione. Tabella informativa sugli eventi dei form Tipo di dati Dati modello Dati normalizzati Dati vista Valore Dati normalizzati ritrasformati usando un trasformatore di modello Come in FormEvents::POST_SUBMIT Dati normalizzati trasformati usando un trasformatore di vista A questo punto, non si possono aggiungere o rimuovere campi dal form. FormEvents::POST_SUBMIT nel componente Form New in version.: L'estensione per raccogliere i dati è stata introdotta in Symfony.. DataCollectorListener ascolta l'evento FormEvents::POST_SUBMIT, per poter raccogliere informazioni sui form. ValidationListener ascolta l'evento FormEvents::POST_SUBMIT, per poter validare automaticamente l'oggetto denormalizzato e aggiornare la rappresentazione normalizzata e quella della vista. Registrare ascoltatori o sottoscrittori di eventi Per poter usare gli eventi dei form, occorre creare un ascoltatore di eventi o un sottoscrittore di eventi, quindi fargli ascoltare un evento. Il nome di ogni evento è definito come costante della classe FormEvents. Inoltre, ciascun callback dell'evento (metodo ascoltatore o sottoscrittore) riceve un singolo parametro, che è un'istanza di FormEvent. L'oggetto evento contiene un riferimento allo stato corrente del form e ai dati correnti in corso di processamento generated on August, 0 Chapter : Eventi dei form
219 Nome Costante FormEvents Dati evento form.pre_set_data FormEvents::PRE_SET_DATA Dati modello form.post_set_data FormEvents::POST_SET_DATA Dati modello form.pre_bind FormEvents::PRE_SUBMIT Dati richiesta form.bind FormEvents::SUBMIT Dati normalizzati form.post_bind FormEvents::POST_SUBMIT Dati vista New in version.: Prima di Symfony., FormEvents::PRE_SUBMIT, FormEvents::SUBMIT e FormEvents::POST_SUBMIT si chiamavano FormEvents::PRE_BIND, FormEvents::BIND e FormEvents::POST_BIND. Le costanti FormEvents::PRE_BIND, FormEvents::BIND e FormEvents::POST_BIND saranno rimosse nella versione.0 di Symfony. I nomi degli eventi mantengono i valori originali, quindi assicurarsi di usare le costanti FormEvents, per compatibilità futura. Ascoltatori di eventi Un ascoltatore di eventi può essere un qualsiasi tipo di callable valido. Creare un ascoltatore di eventi e legarlo al form è molto facile: Listing use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; $form = $formfactory->createbuilder() ->add('username', 'text') ->add('show_ ', 'checkbox') ->addeventlistener(formevents::pre_submit, function (FormEvent $event) $user = $event->getdata(); $form = $event->getform(); if (!$user) return; // Verifica se l'utente ha scelto di mostrare la sua . // Se i dati sono stati già inviati, il valore addizionale che è incluso // nelle variabili della richiesta va rimosso. if (true === $user['show_ ']) $form->add(' ', ' '); else unset($user[' ']); $event->setdata($user); ) ->getform(); dopo aver creato una classe tipo form, si può usare uno dei suoi metodi come callback, per maggiore leggibilità: generated on August, 0 Chapter : Eventi dei form
220 Listing - 0 class SubscriptionType extends AbstractType public function buildform(formbuilderinterface $builder, array $options) $builder->add('username', 'text'); $builder->add('show_ ', 'checkbox'); $builder->addeventlistener( FormEvents::PRE_SET_DATA, array($this, 'onpresetdata') ); public function onpresetdata(formevent $event) Sottoscrittori di eventi I sottoscrittori di eventi hanno vari usi: Migliorare la leggibilità; Ascoltare più eventi; Raggruppare più ascoltatori in una singola classe. Listing use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; class Add FieldListener implements EventSubscriberInterface public static function getsubscribedevents() return array( FormEvents::PRE_SET_DATA => 'onpresetdata', FormEvents::PRE_SUBMIT => 'onpresubmit', ); public function onpresetdata(formevent $event) $user = $event->getdata(); $form = $event->getform(); // Verifica se l'utente dei dati iniziali ha scelto // di mostrare la sua . if (true === $user->isshow ()) $form->add(' ', ' '); public function onpresubmit(formevent $event) generated on August, 0 Chapter : Eventi dei form 0
221 0 0 $user = $event->getdata(); $form = $event->getform(); if (!$user) return; // Verifica se l'utente ha scelto di mostrare la sua . // Se i dati sono stati già inviati, il valore addizionale che è incluso // nelle variabili della richiesta va rimosso. if (true === $user['show_ ']) $form->add(' ', ' '); else unset($user[' ']); $event->setdata($user); Per registrare il sottoscrittore di eventi, usare il metodo addeventsubscriber(): Listing - $form = $formfactory->createbuilder() ->add('username', 'text') ->add('show_ ', 'checkbox') ->addeventsubscriber(new Add FieldListener()) ->getform(); generated on August, 0 Chapter : Eventi dei form
222 Chapter Il componente HttpFoundation Il componente HttpFoundation definisce un livello orientato agli oggetti per le specifiche HTTP. In PHP, la richiesta è rappresentata da alcune variabili globali ($_GET, $_POST, $_FILE, $_COOKIE, $_SESSION...) e la risposta è generata da alcune funzioni (echo, header, setcookie,...). Il componente HttpFoundation di Symfony sostituisce queste variabili globali e queste funzioni di PHP con un livello orientato agli oggetti. Installazione Si può installare il componente in due modi: Installarlo tramite Composer (symfony/http-foundation su Packagist ); Usare il repository ufficiale su Git ( ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Richiesta Il modo più comune per creare una richiesta è basarla sulle variabili attuali di PHP, con createfromglobals() : Listing generated on August, 0 Chapter : Il componente HttpFoundation
223 use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); che è quasi equivalente al più verboso, ma anche più flessibile, construct() : Listing - $request = new Request( $_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER ); Accedere ai dati della richiesta Un oggetto richiesta contiene informazioni sulla richiesta del client. Si può accedere a queste informazioni tramite varie proprietà pubbliche: request: equivalente di $_POST; query: equivalente di $_GET ($request->query->get('name')); cookies: equivalente di $_COOKIE; attributes: non ha equivalenti, è usato dall'applicazione per memorizzare altri dati (vedere sotto) files: equivalente di $_FILE; server: equivalente di $_SERVER; headers: quasi equivalente di un sottinsieme di $_SERVER ($request->headers- >get('content-type')). Ogni proprietà è un'istanza di ParameterBag (o di una sua sotto-classe), che è una classe contenitore: request: ParameterBag ; query: ParameterBag ; cookies: ParameterBag ; attributes: ParameterBag ; files: FileBag 0 ; server: ServerBag ; headers: HeaderBag. Tutte le istanze di ParameterBag hanno metodi per recuperare e aggiornare i propri dati: all() : Restituisce i parametri; keys() : Restituisce le chiavi dei parametri;. construct generated on August, 0 Chapter : Il componente HttpFoundation
224 replace() : Sostituisce i parametri attuali con dei nuovi; add() : Aggiunge parametri; get() : Restituisce un parametro per nome; set() : Imposta un parametro per nome; has() 0 : Restituisce true se il parametro è definito; remove() : Rimuove un parametro. La classe ParameterBag ha anche alcuni metodi per filtrare i valori in entrata: getalpha() : Restituisce i caratteri alfabetici nel valore del parametro; getalnum() : Restituisce i caratteri alfabetici e i numeri nel valore del parametro; getdigits() : Restituisce i numeri nel valore del parametro; getint() : Restituisce il valore del parametro convertito in intero; filter() : Filtra il parametro, usando la funzione PHP filter_var(). Tutti i getter accettano tre parametri: il primo è il nome del parametro e il secondo è il valore predefinito, da restituire se il parametro non esiste: Listing - 0 // la query string è '?foo=bar' $request->query->get('foo'); // restituisce bar $request->query->get('bar'); // restituisce null $request->query->get('bar', 'bar'); // restituisce 'bar' Quando PHP importa la query della richiesta, gestisce i parametri della richiesta, come foo[bar]=bar, in modo speciale, creando un array. In questo modo, si può richiedere il parametro foo e ottenere un array con un elemento bar. A volte, però, si potrebbe volere il valore del nome "originale" del parametro: foo[bar]. Ciò è possibile con tutti i getter di ParameterBag, come get(), tramite il terzo parametro: Listing - 0 // la query string è '?foo[bar]=bar' $request->query->get('foo'); // restituisce array('bar' => 'bar') $request->query->get('foo[bar]'); // restituisce null $request->query->get('foo[bar]', null, true); // restituisce 'bar' generated on August, 0 Chapter : Il componente HttpFoundation
225 Infine, ma non meno importante, si possono anche memorizzare dati aggiuntivi nella richiesta, grazie alla proprietà pubblica attributes, che è anche un'istanza di ParameterBag. La si usa soprattutto per allegare informazioni che appartengono alla richiesta e a cui si deve accedere in diversi punti dell'applicazione. Per informazioni su come viene usata nel framework Symfony, vedere il libro. Infine, si può accedere ai dati grezzi inviati nel corpo della richiesta usando getcontent() 0 : Listing - $content = $request->getcontent(); Questo potrebbe essere utile, per esempio, per processare una stringa JSON inviata all'applicazione da un servizio remoto tramite metodo HTTP POST. Identificare una richiesta Nella propria applicazione, serve un modo per identificare una richiesta. La maggior parte delle volte, lo si fa tramite il "path info" della richiesta, a cui si può accedere tramite il metodo getpathinfo() : Listing - // per una richiesta a // path info è "/post/hello-world" $request->getpathinfo(); Simulare una richiesta Invece di creare una richiesta basata sulle variabili di PHP, si può anche simulare una richiesta: Listing - $request = Request::create( '/hello-world', 'GET', array('name' => 'Fabien') ); Il metodo create() crea una richiesta in base a path info, un metodo e alcuni parametri (i parametri della query o quelli della richiesta, a seconda del metodo HTTP) e, ovviamente, si possono forzare anche tutte le altre variabili (Symfony crea dei valori predefiniti adeguati per ogni variabile globale di PHP). In base a tale richiesta, si possono forzare le variabili globali di PHP tramite overrideglobals() : Listing - $request->overrideglobals(); Si può anche duplicare una query esistente, tramite duplicate(), o cambiare molti parametri con una singola chiamata a initialize() generated on August, 0 Chapter : Il componente HttpFoundation
226 Accedere alla sessione Se si ha una sessione allegata alla richiesta, vi si può accedere tramite il metodo getsession(). Il metodo hasprevioussession() dice se la richiesta contiene una sessione, che sia stata fatta partire in una delle richieste precedenti. Accedere ai dati degli header Accept-* Si può accedere facilmente ai dati di base estratti dagli header Accept-* usando i seguenti metodi: getacceptablecontenttypes() : restituisce la lista dei tipi di contenuto accettati, ordinata per qualità discendente; getlanguages() : restituisce la lista delle lingue accettate, ordinata per qualità discendente getcharsets() 0 : restituisce la lista dei charset accettati, ordinata per qualità discendente getencodings() : restituisce la lista delle codifiche accettate, ordinata per qualità discendente New in version.: Il metodo getencodings() è stato introdotto in Symfony.. Se occorre pieno accesso ai dati analizzati da Accept, Accept-Language, Accept-Charset o Accept- Encoding, si può usare la classe AcceptHeader : Listing - 0 use Symfony\Component\HttpFoundation\AcceptHeader; $accept = AcceptHeader::fromString($request->headers->get('Accept')); if ($accept->has('text/html')) $item = $accept->get('text/html'); $charset = $item->getattribute('charset', 'utf-'); $quality = $item->getquality(); // accepts items are sorted by descending quality $accepts = AcceptHeader::fromString($request->headers->get('Accept')) ->all(); Accedere ad altri dati La classe Request ha molti altri metodi, che si possono usare per accedere alle informazioni della richiesta. Si dia uno sguardo alle API di Request per maggiori informazioni. Sovrascrivere la richiesta New in version.: Il metodo setfactory() è stato introdotto in Symfony.. La classe Request non andrebbe sovrascritta, perché è un oggetto che rappresenta un messaggio HTTP. Ma, migrando da un sistema obsoleto, l'aggiunta di metodi o la modifica di alcuni comportamenti generated on August, 0 Chapter : Il componente HttpFoundation
227 potrebbero aiutare. In questo caso, registrare un callable che sia in grado di creare un'istanza della classe Request: Listing use Symfony\Component\HttpFoundation\Request; Request::setFactory(function ( array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null ) return SpecialRequest::create( $query, $request, $attributes, $cookies, $files, $server, $content ); ); $request = Request::createFromGlobals(); Risposta Un oggetto Response contiene tutte le informazioni che devono essere rimandate al client, per una data richiesta. Il costruttore accetta fino a tre parametri: il contenuto della risposta, il codice di stato e un array di header HTTP: Listing - use Symfony\Component\HttpFoundation\Response; $response = new Response( 'Contenuto', Response::HTTP_OK, array('content-type' => 'text/html') ); New in version.: Il supporto per le costanti dei codici di stato HTTP è stato introdotto in Symfony.. Queste informazioni possono anche essere manipolate dopo la creazione di Response: Listing - $response->setcontent('ciao mondo'); // l'attributo pubblico headers è un ResponseHeaderBag $response->headers->set('content-type', 'text/plain'); $response->setstatuscode(0); Quando si imposta il Content-Type di Response, si può impostare il charset, ma è meglio impostarlo tramite il metodo setcharset() :. generated on August, 0 Chapter : Il componente HttpFoundation
228 Listing - $response->setcharset('iso--'); Si noti che Symfony presume che le risposte siano codificate in UTF-. Inviare la risposta Prima di inviare la risposta, ci si può assicurare che rispetti le specifiche HTTP, richiamando il metodo prepare() : Listing - $response->prepare($request); Inviare la risposta al client è quindi semplice, basta richiamare send() : Listing - $response->send(); Impostare cookie Si possono manipolare i cookie della risposta attraverso l'attributo pubblico headers: Listing - use Symfony\Component\HttpFoundation\Cookie; $response->headers->setcookie(new Cookie('pippo', 'pluto')); Il metodo setcookie() accetta un'istanza di Cookie 0 come parametro. Si può pulire un cookie tramite il metodo clearcookie(). Gestire la cache HTTP La classe Response ha un corposo insieme di metodi per manipolare gli header HTTP relativi alla cache: setpublic() ; setprivate() ; expire() ; setexpires() ; setmaxage() ; setsharedmaxage() ; setttl() ; setclientttl() 0 ; setlastmodified() ; generated on August, 0 Chapter : Il componente HttpFoundation
229 setetag() ; setvary() ; Il metodo setcache() può essere usato per impostare le informazioni di cache più comuni, con un'unica chiamata: Listing - $response->setcache(array( 'etag' => 'abcdef', 'last_modified' => new \DateTime(), 'max_age' => 00, 's_maxage' => 00, 'private' => false, 'public' => true, )); Per verificare che i validatori della risposta (ETag, Last-Modified) corrispondano a un valore condizionale specificato nella richiesta del client, usare il metodo isnotmodified() : Listing - if ($response->isnotmodified($request)) $response->send(); Se la risposta non è stata modificata, imposta il codice di stato a 0 e rimuove il contenuto effettivo della risposta. Rinviare l'utente Per rinviare il client a un altro URL, si può usare la classe RedirectResponse : Listing - use Symfony\Component\HttpFoundation\RedirectResponse; $response = new RedirectResponse(' Flusso di risposta La classe StreamedResponse consente di inviare flussi di risposte al client. Il contenuto della risposta viene rappresentato da un callable PHP, invece che da una stringa: Listing -0 use Symfony\Component\HttpFoundation\StreamedResponse; $response = new StreamedResponse(); $response->setcallback(function () echo 'Ciao mondo'; flush(); sleep(); echo 'Ciao mondo'; flush(); generated on August, 0 Chapter : Il componente HttpFoundation
230 0 ); $response->send(); La funzione flush() non esegue il flush del buffer. Se è stato richiamato ob_start() in precedenza oppure se l'opzione output_buffering è abilitata in php.ini, occorre richiamare ob_flush() prima di flush(). Inoltre, PHP non è l'unico livello possibile di buffer dell'output. Il server web può anche eseguire un buffer, a seconda della configurazione. Ancora, se si usa fastcgi, non si può disabilitare affatto il buffer. Scaricare file Quando si scarica un file, occorre aggiungere un header Content-Disposition alla risposta. Sebbene la creazione di questo header per scaricamenti di base sia facile, l'uso di nomi di file non ASCII è più complesso. Il metodo Symfony\Component\HttpFoundation\Response:makeDisposition() astrae l'ingrato compito dietro una semplice API: Listing - use Symfony\Component\HttpFoundation\ResponseHeaderBag; $d = $response->headers->makedisposition( ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'foo.pdf' ); $response->headers->set('content-disposition', $d); In alternativa, se si sta servendo un file statico, si può usare BinaryFileResponse : Listing - use Symfony\Component\HttpFoundation\BinaryFileResponse $file = 'percorrso/del/file.txt'; $response = new BinaryFileResponse($file); BinaryFileResponse gestirà automaticamente gli header Range e If-Range della richiesta. Supporta anche X-Sendfile (vedere per Nginx 0 e Apache ). Per poterlo usare, occorre determinare se l'header X- Sendfile-Type sia fidato o meno e richiamare trustxsendfiletypeheader() in caso positivo: Listing - $response::trustxsendfiletypeheader(); Si può ancora impostare il Content-Type del file inviato o cambiarne il Content-Disposition: Listing - $response->headers->set('content-type', 'text/plain'); $response->setcontentdisposition( ResponseHeaderBag::DISPOSITION_ATTACHMENT,. Response:makeDispositio.html#method_Symfony\Component\HttpFoundation\Response:makeDisposition generated on August, 0 Chapter : Il componente HttpFoundation 0
231 ); 'nomefile.txt' New in version.: Il metodo deletefileaftersend() è stato introdotto in Symfony.. È possibile eliminare il file dopo l'invio della richiesta, con il metodo deletefileaftersend(). Si noti che questo non funzionerà se è impostato l'header X-Sendfile. Creare una risposta JSON Si può creare qualsiasi tipo di risposta tramite la classe Response, impostando il contenuto e gli header corretti. Una risposta JSON può essere come questa: Listing - use Symfony\Component\HttpFoundation\Response; $response = new Response(); $response->setcontent(json_encode(array( 'data' =>, ))); $response->headers->set('content-type', 'application/json'); C'è anche un'utile classe JsonResponse, che può rendere le cose ancora più semplici: Listing - use Symfony\Component\HttpFoundation\JsonResponse; $response = new JsonResponse(); $response->setdata(array( 'data' => )); Il risultato è una codifica dell'array di dati in JSON, con header Content-Type impostato a application/ json. Per evitare un Hijacking JSON XSSI, bisogna passare un array associativo come parte più esterna dell'array a JsonResponse e non un array indicizzato, in modo che il risultato finale sia un oggetto (p.e. "oggetto": "non dentro un array") invece che un array (p.e. ["oggetto": "dentro un array"]). Si leggano le linee guida OWASP per maggiori informazioni. Solo i metodi che rispondono a richieste GET sono vulnerabili a 'JSON Hijacking' XSSI. I metodi che rispondono a richieste POST restano immuni. Callback JSONP Se si usa JSONP, si può impostare la funzione di callback a cui i dati vanno passati: Listing - $response->setcallback('handleresponse'); In tal caso, l'header Content-Type sarà text/javascript e il contenuto della risposta sarà come questo: generated on August, 0 Chapter : Il componente HttpFoundation
232 Listing - handleresponse('data': ); Sessioni Le informazioni sulle sessioni sono nell'apposito documento: Gestione della sessione. generated on August, 0 Chapter : Il componente HttpFoundation
233 Chapter Gestione della sessione Il componente HttpFoundation di Symfony ha un sotto-sistema per le sessioni molto potente e flessibile, progettato per fornire una gestione delle sessioni tramite una semplice interfaccia orientata agli oggetti, usando una varietà di driver per memorizzare la sessione. Le sessioni sono usate tramite la semplice implementazione Session dell'interfaccia SessionInterface. Assicurarsi che la sessione di PHP non sia già partita, prima di usare la classe Session. Se si ha un vecchio sistema che fa partire una sessione, vedere http_foundation/session_php_bridge.html Un rapido esempio: Listing - 0 use Symfony\Component\HttpFoundation\Session\Session; $session = new Session(); $session->start(); // imposta e ottiene attributi di sessione $session->set('name', 'Drak'); $session->get('name'); // imposta messaggi flash $session->getflashbag()->add('notice', 'Profilo aggiornato'); // recupera i messaggi foreach ($session->getflashbag()->get('notice', array()) as $message) echo "<div class='flash-notice'>$message</div>"; generated on August, 0 Chapter : Gestione della sessione
234 Le sessioni di Symfony sono pensate per sostituire diverse funzioni native di PHP. Le applicazioni devono evitare l'uso di session_start(), session_regenerate_id(), session_id(), session_name() e session_destroy(), usando invece le API della sezione seguente. Pur essendo raccomandato di far partire esplicitamente una sessione, alcune sessioni in effetti partiranno su richiesta, nel caso in cui venga eseguita una richiesta di leggere o scrivere dati di sessione. Le sessioni di Symfony sono incompatibili con la direttiva ini di PHP session.auto_start =. Tale direttiva andrebbe disattivata in php.ini, nelle direttive del server web o in.htaccess. API delle sessioni La classe Session implementa SessionInterface. La classe Session ha una semplice API, suddivisa in un paio di gruppi. Flusso della sessione start() Fa partire la sessione. Non usare session_start(). migrate() Rigenera l'id di sessione. Non usare session_regenerate_id(). Questo metodo, facoltativamente, può cambiare la scadenza del nuovo cookie, che sarà emesso alla chiamata di questo metodo. invalidate() Pulisce i dati della sessione e rigenera la sessione. Non usare session_destroy(). getid() 0 Restituisce l'id della sessione. Non usare session_id(). setid() Imposta l'id della sessione. Non usare session_id(). getname() Restituisce il nome della sessione. Non usare session_name(). setname() Imposta il nome della sessione. Non usare session_name() generated on August, 0 Chapter : Gestione della sessione
235 Attributi della sessione set() Imposta un attributo per chiave. get() Restituisce un attributo per chiave. all() Restituisce tutti gli attributi, come array chiave => valore. has() Restituisce true se l'attributo esiste. replace() Imposta molti attributi contemporaneamente: accetta un array e imposta ogni coppia chiave => valore. remove() Cancella un attributo per chiave. clear() 0 Pulisce tutti gli attributi. Gli attributi sono memorizzati internamente in un "Bag", un oggetto PHP che agisce come un array. Ci sono alcuni metodi per la gestione del "Bag": registerbag() Registra una SessionBagInterface. getbag() Restituisce una SessionBagInterface per nome del bag. getflashbag() Restituisce la FlashBagInterface. Questa è solo una scorciatoia. Meta-dati della sessione getmetadatabag() Restituisce la StorageMetadataBag, che contiene informazioni sulla sessione generated on August, 0 Chapter : Gestione della sessione
236 Gestori del salvataggio La gestione delle sessioni di PHP richiede l'uso della variabile $_SESSION, tuttavia questo interferisce in qualche modo con la testabilità e l'incapsulamento del codice in un paradigma OOP. Per superare questo problema, Symfony usa delle "bag" di sessione, collegate alla sessione, che incapsulano dati specifici di "attributi" o "messaggi flash". Questo approccio mitiga anche l'inquinamento dello spazio dei nomi all'interno di $_SESSION, perché ogni bas memorizza i suoi dati sotto uno spazio dei nomi univoco. Questo consente a Symfony di coesistere in modo pacifico con altre applicazioni o librerie che potrebbero usare $_SESSION, mantenendo tutti i dati completamente compatibili con la gestione delle sessioni di Symfony. Symfony fornisce due tipi di bag, con due implementazioni separate. Ogni cosa è scritta su interfacce, quindi si può estendere o creare i propri tipi di bag, se necessario. SessionBagInterface ha la seguente API, intesa principalmente per scopi interni: getstoragekey() 0 restituisce la chiave che il bag memorizzerà nell'array sotto $_SESSION. In generale questo valore può essere lasciato al suo predefinito ed è per uso interno. initialize() richiamato internamente dalle classi memorizzazione della sessione di Symfony per collegare i dati del bag alla sessione. getname() Restituisce il nome del bag della sessione. Attributi Lo scopo dei bag che implementano AttributeBagInterface è gestire la memorizzazione degli attributi di sessione. Questo potrebbe includere cose come l'id utente, le impostazioni "ricordami" o altre informazioni basate sullo stato dell'utente. AttributeBag è l'implementazione standard predefinita. NamespacedAttributeBag consente agli attributi di essere memorizzati in uno spazio dei nomi strutturato. Qualsiasi sistema di memorizzazione chiave => valore è limitato riguardo alla complessità dei dati che possono essere memorizzati, perché ogni chiave deve essere univoca. Si può ottenere una sorta di spazio di nomi, introducendo una convenzione di nomi nelle chiavi, in modo che le varie parti dell'applicazioni possano operare senza interferenze. Per esempio, modulo.pippo e modulo.pippo. Tuttavia, a volte questo non è molto pratico quando gli attributi sono array, per esempio un insieme di token. In questo caso, gestire l'array diventa pesante, perché di deve recuperare l'array e poi processarlo e memorizzarlo di nuovo: Listing - $tokens = array( 'tokens' => array( generated on August, 0 Chapter : Gestione della sessione
237 ); ), 'a' => 'ace0b', 'b' => 'fabf', Quindi ogni processamento può rapidamente diventare brutto, persino la semplice aggiunta di un token all'array: Listing - $tokens = $session->get('tokens'); $tokens['c'] = $value; $session->set('tokens', $tokens); Con uno spazio di nomi strutturato, la chiave può essere tradotta nella struttura dell'array, usando un carattere che crei lo spazio dei nomi (predefinito a /): Listing - $session->set('tokens/c', $value); In questo modo si può accedere facilmente a una chiave nell'array direttamente e facilmente. AttributeBagInterface ha una semplice API set() Imposta un attributo per chiave. get() Restituisce un attributo per chiave. all() Restituisce tutti gli attributi come array chiave => valore. has() 0 Restituisce true se l'attributo esiste. keys() Restituisce un array di chiavi di attributi. replace() Imposta molti attributi contemporaneamente: accetta un array e imposta ogni coppia chiave => valore. remove() Cancella un attributo per chiave. clear() Pulisce il bag generated on August, 0 Chapter : Gestione della sessione
238 Messaggi flash Lo scopo di FlashBagInterface è fornire un modo di impostare e recuperare messaggi basati sulla sessione. Il flusso dei messaggi flash di solito è impostarli in una richiesta e mostrarli dopo il rinvio di una pagina. Per esempio, un utente invia un form che esegue un controllore che aggiorna un dato e dopo il processo il controllore rinvia o alla pagina di aggiornamento o a quella di errore. I messaggi flash impostati nella pagina precedente sarebbero mostrati immediatamente nella pagina successiva. Tuttavia questa è solo una possibile applicazione per i messaggi flash. AutoExpireFlashBag con questa implementazione, i messaggi impostati in una pagina saranno disponibili per essere mostrati sono al caricamento della pagina successiva. Tali messaggi scadranno automaticamente, che siano stati recuperati o meno. FlashBag con questa implementazione, i messaggi rimarranno i sessione finché non saranno esplicitamente recuperati o rimossi. Questo rende possibile l'utilizzo della cache ESI. FlashBagInterface ha una semplice API add() aggiunge un messaggio flash alla pila del tipo specificato. set() 0 imposta i flash per tipo. Questo metodo accetta sia messaggi singoli come stringa, che messaggi multipli come array. get() restituisce i flash per tipo e cancella tali flash dal bag. setall() imposta tutti i flash, accetta un array di array con chiavi tipo => array(messaggi). all() restituisce tutti i flash (come array di array con chiavi) e cancella i flash dal bag. peek() restituisce i flash per tipo (sola lettura). peekall() restituisce tutti i flash (sola lettura) come array di array con chiavi. has() restituisce true se il tipo esiste, false altrimenti generated on August, 0 Chapter : Gestione della sessione
239 keys() restituisce un array di tipi di flash memorizzati. clear() pulisce il bag. Solitamente, per applicazioni semplici basta avere un solo messaggio flash per tipo, per esempio una nota di conferma dopo l'invio di un form. Tuttavia, i messaggi flash sono memorizzati in un array per $type, il che vuol dire che l'applicazione può inviare più messaggi di un dato tipo. Questo consente l'uso dell'api per messaggi più complessi. Esempi di impostazioni di flash multipli: Listing - 0 use Symfony\Component\HttpFoundation\Session\Session; $session = new Session(); $session->start(); // aggiunge i messaggi flash $session->getflashbag()->add( 'warning', 'Il file di config è scrivibile, dovrebbe essere in sola lettura' ); $session->getflashbag()->add('error', 'Aggiornamento del nome fallito'); $session->getflashbag()->add('error', 'Un altro errore'); Si potrebbero mostrare i messaggi in questo modo: Semplice, mostra un tipo di messaggio: Listing - // mostra avvertimenti foreach ($session->getflashbag()->get('warning', array()) as $message) echo '<div class="flash-warning">'.$message.'</div>'; // mostra errori foreach ($session->getflashbag()->get('error', array()) as $message) echo '<div class="flash-error">'.$message.'</div>'; Metodo compatto per processare la visualizzazione di tutti i flash in un colpo solo: Listing - foreach ($session->getflashbag()->all() as $type => $messages) foreach ($messages as $message) echo '<div class="flash-'.$type.'">'.$message.'</div>';. generated on August, 0 Chapter : Gestione della sessione
240 Chapter 0 Configurare sessioni e gestori di salvataggio Questa sezione tratta la configurazione della gestione della sessione e la messa a punto secondo esigenze specifiche. Questa documentazione copre alcuni gestori, che memorizzano e recuperano dati di sessione, e la configurazione del comportamento della sessione. Gestori del salvataggio Il flusso della sessione di PHP ha sei possibili operazioni da eseguire. La sessione normale segue open, read, write e close, con la possibilità di destroy e gc (garbage collection, che fa scadere le vecchie sessioni: gc viene richiamato in modo casuale, secondo la configurazione di PHP, e, se richiamato, è invocato dopo l'operazione open). Si può approfondire l'argomento su php.net/session.customhandler Gestori del salvataggio nativi di PHP I gestori cosiddetti 'nativi' sono gestori di sessione che sono o compilati in PHP o forniti da estensioni di PHP, come PHP-Sqlite, PHP-Memcached e così via. Tutti i gestori di salvataggio nativi sono interni a PHP e quindi non hanno API pubbliche. Vanno configurati tramite direttive ini, solitamente session.save_path, e potenzialmente altre direttive specifiche. Si possono trovare altri dettagli nei docblock dei metodi setoptions() di ciascuna classe. Per esempio, quello fornito dall'estensione Memcached si può trovare in php.net/memcached.setoption. Sebbene i gestori di salvataggio nativi possano essere attivati direttamente, usando ini_set('session.save_handler', $nome);, Symfony fornisce un modo conveniente per attivarli nello stesso modo dei gestori personalizzati. Symfony fornisce driver per i gestori nativi, come per esempio: NativeFileSessionHandler generated on August, 0 Chapter 0: Configurare sessioni e gestori di salvataggio 0
241 Esempio di utilzzo: Listing 0- use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; $storage = new NativeSessionStorage(array(), new NativeFileSessionHandler()); $session = new Session($storage); Con l'eccezione del gestore files, nativo di PHP e sempre disponibile, la disponibilità di altri gestori dipende da quali estensioni PHP sono attive a runtime. I gestori di salvataggio nativi forniscono una soluzione rapida alla memorizzazione di sessioni, tuttavia in sistemi complessi, in cui occorre maggior controllo, i gestori di salvataggio personalizzati possono fornire più libertà e flessibilità. Symfony fornisce varie implementazioni, personalizzabili a piacimento. Gestori di salvataggio personalizzati I gestori personalizzati sono quelli che sostituiscono completamente i gestori del salvataggio nativi di PHP, fornendo sei funzioni di callback, richiamate internamente da PHP in vari punti del flusso della sessione. HttpFoundation di Symfony ne fornisce alcuni predefiniti, che possono facilmente servire da esempi, se se ne vuole scrivere uno. PdoSessionHandler MemcacheSessionHandler MemcachedSessionHandler MongoDbSessionHandler NullSessionHandler Esempio: Listing 0- use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; $pdo = new \PDO(...); $storage = new NativeSessionStorage(array(), new PdoSessionHandler($pdo)); $session = new Session($storage); generated on August, 0 Chapter 0: Configurare sessioni e gestori di salvataggio
242 Configurare le sessioni di PHP NativeSessionStorage può configurare la maggior parte delle direttive di php.ini documentate su php.net/session.configuration 0. Per configurare tali impostazioni, passare le chiavi (omettendo la parte session. iniziale della chiave) come array chiave-valore al parametro $options del costruttore. Oppure impostarle tramite il metodo setoptions(). Per ulteriore chiarezza, alcune chiavi di opzioni sono spiegate in questa documentazione. Scadenza del cookie di sessione Per sicurezza, generalmente si raccomanda di inviare i token di sessione come cookie. SI può configurare la scadenza dei cookie di sessione, specificando il tempo (in secondi) usando la chiave cookie_lifetime nel parametro $options del costruttore di NativeSessionStorage. Impostare cookie_lifetime a 0 farà sì che il cookie durerà solo finché il browser non resta aperto. Generalmente, cookie_lifetime andrebbe impostato a un numero relativamente grande di giorni, settimane o mesi. Non è raro impostare i cookie per un anno o più, a seconda dell'applicazione. Essendo i cookie di sessione dei token esclusivamente lato client, sono meno importanti nel controllo dei dettagli rispetto alle impostazioni di sicurezza, che alla fine possono essere controllate con tranquillità solamente lato server. L'impostazione cookie_lifetime è il numero di secondi per cui il cookie sarà valido, non è un timestamp Unix. Il cookie di sessione risultante sarà emesso con un tempo di scadenza di time() + cookie_lifetime, con riferimento alla data del server. Configurare il garbage collector Quando si apre una sessione, PHP richiamerà gc in modo casuale, in base alla probabilità impostata da session.gc_probability / session.gc_divisor. Per esempio, se impostati rispettivamente a /00, risulterebbe in una probabilità del %. In modo simile, / vorrebbe dire possibilità su di essere richiamato, quindi il %. Se il garbage collector viene invocato, PHP passerà il valore memorizzato nella direttiva php.ini session.gc_maxlifetime. Il significato in questo contesto è che ogni sessione memorizzata prima di maxlifetime secondi fa andrebbe cancellata. Questo consente di far scadere le sessioni in base al tempo di inattività. Si possono impostare queste impostazioni passando gc_probability, gc_divisor e gc_maxlifetime in un array al costruttore di NativeSessionStorage o al metodo setoptions() generated on August, 0 Chapter 0: Configurare sessioni e gestori di salvataggio
243 Scadenza della sessione Quando viene creata una nuova sessione, quindi quando Symfony invia un nuovo cookie di sessione al client, il cookie sarà emesso con un tempo di scadenza. Questo tempo è calcolato aggiungendo il valore di configurazione di PHP in session.cookie_lifetime al tempo attuale del server. PHP invierà un cookie una sola volta. Ci si aspetta che il client memorizzi tale cookie per l'intero periodo. Sarà inviato un nuovo cookie solo quando la sessione viene distrutta, il cookie viene cancellato dal browser oppure l'id della sessione viene rigenerato, usando i metodi migrate() o invalidate() della classe Session. Il tempo di scadenza iniziale del cookie può essere impostato configurando NativeSessionStorage, usando il metodo setoptions(array('cookie_lifetime' => )). Un tempo di scadenza del cookie di 0 vuol dire che il cookie scadrà alla chiusura del browser. Tempo di inattività della sessione Spesso, ci sono circostanze in cui si vuole proteggere l'uso della sessione oppure minimizzarne l'uso non autorizzato, quando un utente si allontana dalla sua postazione mentre è loggato, distruggendo la sessione dopo un certo periodo di inattività. Per esempio, solitamente le applicazioni delle banche buttano fuori l'utente dopo appena o 0 minuti di inattività. L'impostazione della scadenza del cookie, in questo caso, non è appropriata, perché potrebbe essere manipolata dal client, quindi occorre farlo scadere lato server. Il modo più facile di farlo è tramite il garbage collector, che viene eseguito con una frequenza ragionevole. Il lifetime del cookie andrebbe impostato a un valore relativamente alto e il maxlifetime del garbage collector andrebbe impostato per distruggere le sessioni al periodo di inattività desiderato. L'altra opzione è verificare specificatamente se una sessione sia scaduta dopo che la sessione parte. La sessione può essere distrutta, come richiesto. Questo metodo può consentire di integrare la scadenza delle sessioni nell'esperienza utente, per esempio, mostrando un messaggio. Symfony registra alcuni meta-dati di base su ogni sessione, per dare completa libertà in quest'area. Meta-dati di sessione Le sessioni vengono decorate da alcuni meta-dati di base, per consentire maggior controllo sulle impostazioni di sicurezza. L'oggetto sessione ha un getter per i meta-dati, getmetadatabag(), che espone un'istanza di MetadataBag : Listing 0- $session->getmetadatabag()->getcreated(); $session->getmetadatabag()->getlastused(); Entrambi i metodi restituiscono un timestamp Unix (relativo al server). Questi meta-dati possono essere usati per far scadere in modo espliciti una sessione all'accesso, p.e.: Listing generated on August, 0 Chapter 0: Configurare sessioni e gestori di salvataggio
244 $session->start(); if (time() - $session->getmetadatabag()->getlastused() > $maxidletime) $session->invalidate(); throw new SessionExpired(); // rinvia alla pagina di sessione scaduta Si può anche specificare a cosa è stato impostato cookie_lifetime per un determinato cookie, usando il metodo getlifetime(): Listing 0- $session->getmetadatabag()->getlifetime(); Il tempo di scadenza del cookie può essere determinato aggiungendo il timestamp creato e il lifetime. Compatibilità con PHP. A partire da PHP..0, sono disponibili SessionHandler e SessionHandlerInterface. Symfony. fornisce compatibilità in avanti per SessionHandlerInterface, in modo che possa essere usata con PHP.. Questo aumenta molto l'interoperabilità con altre librerie. SessionHandler 0 è una classe interna speciale di PHP, che espone i gestori del salvataggio nativi nello user space di PHP. Per poter fornire una soluzione a chi usa PHP., Symfony ha una classe speciale, chiamata NativeSessionHandler, che sotto PHP. estende da SessionHandler e sotto PHP. è solo una classe di base vuota. Questo dà alcune interessanti opportunità, per sfruttare le funzionalità di PHP., se disponibile. Proxy per il gestore del salvataggio Un proxy per il gestore del salvataggio di base avvolge un gestore di salvataggio, che è stato introdotto per supportare senza problemi la migrazione da PHP. a PHP.+. Inoltre, crea un punto di estensione da cui si può aggiungere una logica personalizzata, che funzioni indipendentemente da quale gestore sia all'interno. Ci sono due tipi di classi di proxy per il gestore del salvataggio, che ereditano da AbstractProxy : sono NativeProxy e SessionHandlerProxy. NativeSessionStorage inietta automaticamente i gestori della memorizzazione in un proxy per il gestore del salvataggio, a meno che non ce ne sia già uno che lo avvolge. NativeProxy è usato automaticamente sotto PHP., quando i gestori del salvataggio interni di PHP vengono specificati tramite le classi Native*SessionHandler classes, mentre SessionHandlerProxy sarà usato per avvolgere qualsiasi gestore del salvataggio personalizzato, che implementi SessionHandlerInterface generated on August, 0 Chapter 0: Configurare sessioni e gestori di salvataggio
245 A partire da PHP., tutti i gestori di sessione implementano SessionHandlerInterface, incluse le classi Native*SessionHandler che ereditano da SessionHandler 0. Il meccanismo del proxy consente di essere coinvolto in modo più approfondito nelle classi dei gestori del salvataggio. Un proxy, per esempio, può essere usato per criptare ogni transazione di una sessione, senza sapere nulla del gestore del salvataggio specifico. Prima di PHP., si possono usare proxy di gestori di salvataggio personalizzati, non quelli nativi di PHP generated on August, 0 Chapter 0: Configurare sessioni e gestori di salvataggio
246 Chapter Test con le sessioni Symfony è stato progettato fin dall'inizio con la testabilità in mente. Per rendere facilmente testabile un codice che usa le sessioni, vengono forniti due diversi mock di meccanismi di memorizzazione, sia per test unitari che per test funzionali. È difficile testare codice usando le sessioni reali, perché il flusso dello stato di PHP è globale e non è possibile avere più sessioni in contemporanea nello stesso processo PHP. I mock dei sistemi di memorizzazione simulano il flusso della sessione di PHP senza effettivamente farne partire una, consentendo il test del codice senza complicazioni. Si possono anche eseguire istanze multiple nello stesso processo PHP. I mock dei driver di memorizzazione non leggono o scrivono le funzioni di sistema globali session_id() o session_name(). Ci sono dei metodi per la simulazione, in caso sia necessario: getid() : restituisce l'id di sessione. setid() : imposta l'id di sessione. getname() : restituisce il nome della sessione. setname() : imposta il nome della sessione. Test unitari Per i test unitari in cui non serve persistere la sessione, basta semplicemente cambiare il sistema di memorizzazione predefinito con MockArraySessionStorage : Listing - use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\HttpFoundation\Session\Session; $session = new Session(new MockArraySessionStorage()); generated on August, 0 Chapter : Test con le sessioni
247 Test funzionali Per i test funzionali in cui serve persistere dati di sessione tra processi PHP separati, basta cambiare il sistema di memorizzazione con MockFileSessionStorage : Listing - use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage; $session = new Session(new MockFileSessionStorage());. generated on August, 0 Chapter : Test con le sessioni
248 Chapter Integrazione con sessioni legacy Alle volte potrebbe essere necessario integrare Symfony all'interno di applicazioni preesistenti sulle quali non si ha un sufficiente livello di controllo. Come già visto in altre parti, in Symfony le sessioni sono disegnate in modo da eliminare sia l'utilizzo delle funzioni native PHP session_*() che l'utilizzo dell'array superglobale $_SESSION. Per Symfony, inoltre, far partire la sessione è obbligatorio. Tuttavia, quando ci sono circostanze in cui ciò non sia possibile, si potrà comunque utilizzare lo speciale bridge per la memorizzazione PhpBridgeSessionStorage che è disegnato con l'intento di permettere a Symfony di lavorare con una sessione iniziata all'esterno dell'ambiente delle sessioni di Symfony. Bisogna comunque fare attenzione perché alcuni fattori potrebbero inibirne il funzionamento, come nel caso in cui l'applicazione preesistente dovesse cancellare $_SESSION. Il seguente esempio ne mostra il tipico utilizzo: Listing - 0 use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage; // l'applicazione preesistente configura la sessione ini_set('session.save_handler', 'files'); ini_set('session.save_path', '/tmp'); session_start(); // si fa in modo che Symfony si colleghi alla sessione esistente $session = new Session(new PhpBridgeSessionStorage()); // da ora in poi symfony si interfaccia con la sessione PHP esistente $session->start(); Tutto ciò consente l'utilizzo delle API per la gestione della sessione di Symfony e permette di migrare la propria applicazione per utilizzare le sessioni di Symfony.. generated on August, 0 Chapter : Integrazione con sessioni legacy
249 Le sessioni di Symfony salvano i dati come attributi in una speciale 'sacca' che utilizza una chiave nell'array superglobale $_SESSION. Questo vuol dire che la sessione di Symfony non può accedere a chiavi arbitrarie di $_SESSION, le quali potrebbero appartenere all'applicazione preesistente, anche se tutto il contenuto di $_SESSION verrà salvato quando la sessione viene salvata. generated on August, 0 Chapter : Integrazione con sessioni legacy
250 Chapter Proxy fidati Se si usa il framework Symfony, iniziare leggendo Configurare Symfony per funzionare dietro a un load balancer o a un reverse proxy. Se ci si trova dietro un proxy, come un bilanciatore di carico, è possibile che siano inviate alcune informazioni con gli header speciali X-Forwarded-*. Per esempio, l'header HTTP Host di solito si usa per restituire l'host richiesto. Ma, quando ci si trova dietro a un proxy, il vero host potrebbe trovarsi nell'header X-Forwarded-Host. Poiché gli header HTTP possono essere falsificati, Symfony non si fida degli header dei proxy. Se si è dietro a un proxy, si deve indicare manualmente che tale proxy è fidato. New in version.: È stato aggiunto il supporto alla notazione CIDR, quindi si possono inserire intere sottoreti (p.e /, fc00::/). Listing - use Symfony\Component\HttpFoundation\Request; // fidarsi solo degli header dei proxy che vengono da questo indirizzo IP Request::setTrustedProxies(array('.0.0.', ' /')); Configurare i nomi degli header I seguenti proxy sono fidati per impostazione predefinita: X-Forwarded-For Usato in getclientip() ; X-Forwarded-Host Usato in gethost() ; X-Forwarded-Port Usato in getport() ; X-Forwarded-Proto Usato in getscheme() e issecure() ; generated on August, 0 Chapter : Proxy fidati 0
251 Se un reverse proxy usa un nome diverso per uno di questi header, lo si può configurare tramite settrustedheadername() : Listing - Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X-Proxy-For'); Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X-Proxy-Host'); Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X-Proxy-Port'); Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X-Proxy-Proto'); Diffidare di alcuni header Per impostazione predefinita, se si indica un indirizzo IP di un proxy come fidato, tutti e quattro gli header elencati sopra saranno fidati. Se occorre indicare come fidati alcuni di questi header ma non altri, lo si può fare: Listing - // disabilita la fiducia nell'header ``X-Forwarded-Proto``, sarà usato l'header predefinito Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, '');. generated on August, 0 Chapter : Proxy fidati
252 Chapter Il componente HttpKernel Il componente HttpKernel fornisce un processo strutturato per convertire una Request in una Response, usando il distributore di eventi. È abbastanza flessibile per creare un framework completo (Symfony), un micro-framework (Silex) o un CMS avanzato (Drupal). Installazione È possibile installare il componente in due modi: Installandolo via Composer (symfony/http-kernel su Packagist); Utilizzando il repository ufficiale su Git ( ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Il flusso di una richiesta Ogni interazione HTTP inizia con una richiesta e finisce con una risposta. Il compito dello sviluppatore è quello di creare codice PHP che legga le informazioni della richiesta (p.e. l'url) e crei e restituisca una risposta (p.e. una pagina HTML o una stringa JSON).. generated on August, 0 Chapter : Il componente HttpKernel
253 Di solito, si costruisce una sorta di framework o sistema per gestire tutte le operazioni ripetitive (come rotte, sicurezza, ecc.), in modo che uno sviluppatore possa costruire facilmente ogni pagina dell'applicazione. Come esattamente tali sistemi siano costruiti varia enormemente, Il componente HttpKernel fornisce un'interfaccia che formalizza il processo di iniziare con una richiesta e creare la risposta appropriata. Il componente è pensato per essere il cuore di qualsiasi applicazione o framework, non importa quanto vari l'architettura di tale sistema: Listing - 0 namespace Symfony\Component\HttpKernel; use Symfony\Component\HttpFoundation\Request; interface HttpKernelInterface /** Response Un'istanza di Response */ public function handle( Request $request, $type = self::master_request, $catch = true ); Internamente, HttpKernel::handle(), l'implementazione concreta di HttpKernelInterface::handle(), definisce un flusso che inizia con una Request e finisce con una Response generated on August, 0 Chapter : Il componente HttpKernel
254 I dettagli precisi di tale flusso sono la chiave per capire come funzioni il kernel (e il framework Symfony o qualsiasi altra libreria che usi il kernel). HttpKernel: guidato da eventi Il metodo HttpKernel::handle() funziona internamente distribuendo eventi. Questo rende il metodo flessibile, ma anche un po' astratto, poiché tutto il "lavoro" di un framework/applicazione costruiti con HttpKernel è effettivamente svolto da ascoltatori di eventi. Per aiutare nella spiegazione di questo processo, questo documento ne analizza ogni passo e spiega come funziona una specifica implementazione di HttpKernel, il framework Symfony. All'inizio, l'uso di HttpKernel è molto semplice e implica la creazione un distributore di eventi e un risolutore di controllori (spiegato più avanti). Per completare un kernel funzionante, si aggiungeranno ulteriori ascoltatori agli eventi discussi sotto: Listing - 0 use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\HttpKernel; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\HttpKernel\Controller\ControllerResolver; // creare l'oggetto Request $request = Request::createFromGlobals(); $dispatcher = new EventDispatcher(); aggiungere degli ascoltatori // creare un risolutore di controllori $resolver = new ControllerResolver(); // istanziare il kernel $kernel = new HttpKernel($dispatcher, $resolver); // esegue effettivamente il kernel, che trasforma la richiesta in una risposta // distribuendo eventi, richiamando un controllore e restituendo la risposta $response = $kernel->handle($request);. generated on August, 0 Chapter : Il componente HttpKernel
255 0 // mostra il contenuto e invia gli header $response->send(); // lancia l'evento kernel.terminate $kernel->terminate($request, $response); Vedere "Un esempio funzionante" per un'implementazione più concreta. Per informazioni generali sull'aggiunta di ascoltatori agli eventi qui sotto, vedere Creare un ascoltatore di eventi. Fabien Potencier ha anche scritto una bella serie sull'uso del componente HttpKernel e altri componenti di Symfony per creare un proprio framework. Vedere Create your own framework... on top of the Symfony Components. ) L'evento kernel.request Scopi tipici: Aggiungere più informazioni alla Request, inizializzare le parti del sistema oppure restituire una Response se possibile (p.e. un livello di sicurezza che nega accesso) Tabella informativa sugli eventi del kernel Il primo evento distribuito in HttpKernel::handle è kernel.request, che può avere vari ascoltatori. Gli ascoltatori di questo evento possono essere alquanti vari. Alcuni, come un ascoltatore di sicurezza, possono avere informazioni sufficienti a creare un oggetto Response immediatamente. Per esempio, se un ascoltatore di sicurezza determina che un utente non deve accedere, può restituire un RedirectResponse alla pagina di login o una risposta 0 (accesso negato). Se a questo punto viene restituita una Response, il processo salta direttamente all'evento kernel.response generated on August, 0 Chapter : Il componente HttpKernel
256 Altri ascoltatori inizializzano semplicemente alcune cose o aggiungono ulteriori informazioni alla richiesta. Per esempio, un ascoltatore può determinare e impostare il locale sull'oggetto Request. Un altro ascoltatore comune è il routing. Un ascoltatore di rotte può processare la Request e determinare il controllore da rendere (vedere la sezione successiva). In effetti, l'oggetto Request ha un insieme di "attributi", che è il posto ideale per memorizzare tali dati, ulteriori e specifici dell'applicazione, sulla richiesta. Questo vuol dire che se un ascoltatore di rotte determina in qualche modo il controllore, può memorizzarlo negli attributi di Request (che possono essere usati dal risolutore di controllori). Complessivamente, lo scopo dell'evento kernel.request è quello di creare e restituire una Response direttamente oppure di aggiungere informazioni alla Request (p.e. impostando il locale o altre informazioni sugli attributi della Request). Quando si imposta una risposta per l'evento kernel.request, la propagazione si ferma. Questo vuol dire che ascoltatori con priorità inferiore non saranno eseguiti. kernel.request nel framework Symfony L'ascoltatore più importante di kernel.request nel framework Symfony è RouterListener 0. Questa classe esegue il livello delle rotte, che restituisce un array di informazioni sulla richiesta corrispondente, incluso _controller e ogni segnaposto presente nello schema della rotta (p.e. slug). Vedere il componente Routing. Questo array di informazioni è memorizzato nell'array attributes di Request. L'aggiunta di informazioni sulle rotte in questo punto non porta ancora a nulla, ma sarà usato durante la risoluzione del controllore. ) Risolvere il controllore Ipotizzando che nessun ascoltatore di kernel.request sia stato in grado di creare una Response, il passo successivo in HttpKernel è determinare e preparare (cioè risolvere) il controllore. Il controllore è la parte generated on August, 0 Chapter : Il componente HttpKernel
257 del codice dell'applicazione finale responsabile di creare e restituire la Response per una pagina specifica. L'unico requisito è che sia un callable PHP, cioè una funzione, un metodo su un oggetto o una Closure. Ma come determinare l'esatto controllore per una richiesta è un compito che spetta all'applicazione. Questo è il lavoro del "risolutore di controllori", una classe che implementa ControllerResolverInterface ed è uno dei parametri del costruttore di HttpKernel. Il compito dello sviluppatore è creare una classe che implementi l'interfaccia e quindi i suoi due metodi: getcontroller e getarguments. In effetti, esiste già un'implementazione, che si può usare direttamente o a cui ci si può ispirare: ControllerResolver. Tale implementazione è spiegata qui sotto: Listing - 0 namespace Symfony\Component\HttpKernel\Controller; use Symfony\Component\HttpFoundation\Request; interface ControllerResolverInterface public function getcontroller(request $request); public function getarguments(request $request, $controller); Internamente, il metodo HttpKernel::handle prima richiama getcontroller() sul risolutore di controllori. A questo viene passata la Request ed è responsabile di determinare in qualche modo e restituire un callable PHP (il controllore) in base alle informazioni della richiesta. Il secondo metodo, getarguments(), sarà richiamato dopo che un altro evento, kernel.controller, è stato distribuito generated on August, 0 Chapter : Il componente HttpKernel
258 Risolvere il controllore nel framework Symfony Il framework Symfony usa la classe ControllerResolver (in realtà, usa una sotto-classe, con alcune funzionalità aggiuntive menzionate più avanti). Questa classe sfrutta le informazioni che erano state inserite nella proprietà attributes dell'oggetto Request durante RouterListener. getcontroller ControllerResolver cerca una chiave _controller nella proprietà attributes dell'oggetto Request (si ricordi che tale informazione solitamente è memorizzata nella Request tramite RouterListener). Questa stringa viene trasformata in un callable PHP nei seguenti passi: a) Il formato AcmeDemoBundle:Default:index della chiave _controller viene cambiato in un'altra stringa che contiene il nome completo di classe e metodo del controllore, seguendo la convenzione di Symfony, cioè Acme\DemoBundle\Controller\DefaultController::indexAction. Questa trasformazione è specifica della sotto-classe ControllerResolver usata dal framework Symfony. b) Viene istanziata una nuova istanza della classe controllore, senza parametri al costruttore. c) Se il controllore implementa ContainerAwareInterface, viene richiamato setcontainer sull'oggetto controllore e gli viene passato il contenitore. Anche questo passo è specifico della sotto-classe ControllerResolver usata dal framework Symfony. Ci possono essere alcune piccole variazioni nel processo appena visto, p.e. se i controllori sono stati registrati come servizi). ) L'evento kernel.controller Scopi tipici: Inizializzare cose o cambiare il controllore subito prima che il controllore venga eseguito. Tabella informativa sugli eventi del kernel Dopo che il callable controllore è stato determinato, HttpKernel::handle distribuisce l'evento kernel.controller. Gli ascoltatori di questo evento possono inizializzare alcune parti del sistema che devono essere inizializzate dopo che alcune cose sono state determinate (p.e. il controllore, informazioni sulle rotte) ma prima che il controllore sia eseguito. Per alcuni esempi, vedere la sezione Symfony più avanti generated on August, 0 Chapter : Il componente HttpKernel
259 Gli ascoltatori di questo evento possono anche cambiare completamente il callable controllore, richiamando FilterControllerEvent::setController 0 sull'oggetto evento che viene passato agli ascoltatori di questo evento. kernel.controller in the Symfony Framework Ci sono alcuni ascoltatori minori dell'evento kernel.controller nel framework Symfony, la maggior parte dei quali ha a che vedere con la raccolta di dati per il profilatore, se è abilitato. Un interessante ascoltatore è presente in SensioFrameworkExtraBundle, incluso in Symfony Standard Edition. La di questo bundle consente di passare un oggetto completo (p.e. un oggetto Post) a un controllore, al posto di uno scalare (p.e. un parametro id presente su una rotta). L'ascoltatore, ParamConverterListener, usa reflection per cercare ogni parametro del controllore e prova a usare vari metodi per convertirli in oggetti, che sono quindi memorizzati nella proprietà attributes dell'oggetto Request. Leggere la prossima sezione per vedere perché questo è importante. ) Ottenere i parametri del controllore Quindi, HttpKernel::handle richiama getarguments(). Si ricordi che il controllore restituito in getcontroller è un callable. Lo scopo di getarguments è restituire l'array di parametri che vanno passati a tale controllore. Il modo esatto in cui ciò sia realizzato spetta completamente alla progettazione dello sviluppatore, sebbene la classe ControllerResolver ne sia un buon esempio generated on August, 0 Chapter : Il componente HttpKernel
260 A questo punto il kernel ha un callable PHP (il controllore) e un array di parametri che vanno passati durante l'esecuzione di tale callable. Ottenere i parametri del controllore nel framework Symfony Ora che sappiamo esattamente cosa sia il callable controllore (solitamente un metodo dentro all'oggetto controllore), ControllerResolver usa reflection sul callable per restituire un array di nomi di ciascun parametro. Quindi itera su ogni parametro e usa il trucco seguente per determinare quale valore sia da passare per ogni parametro: a) Se gli attributi di Request contengono una chiave che corrisponde al nome del parametro, viene usato quel valore. Per esempio, se il primo parametro di un controllore è $slug e c'è una chiave slug nella chiave attributes di Request, tale valore viene usato (e tipicamente questo valore viene da RouterListener). b) Se il parametro nel controllore ha specificato come tipo Request, viene passata la Request come valore. ) Richiamare il controllore Il prossimo passo è semplice! HttpKernel::handle esegue il controllore generated on August, 0 Chapter : Il componente HttpKernel 0
261 Il compito del controllore è costruire la risposta per la risorsa data. Potrebbe essere una pagina HTML, una stringa JSON o qualsiasi altra cosa. Diversamente dalle altre parti del processo viste finora, questo passo è implementato dallo sviluppatore, per ogni pagina da costruire. Di solito, il controllore restituirà un oggetto Response. Se questo è vero, il lavoro del kernel sta per finire! In questo caso, il prossimo passo è l'evento kernel.response. Se invece il controllore restituisce qualcosa che non sia una Response, il kernel deve fare ancora un piccolo lavoro, kernel.view (perché lo scopo finale è sempre generare un oggetto Response). Un controllore deve restituire qualcosa. Se un controllore restituisce null, viene immediatamente lanciata un'eccezione. generated on August, 0 Chapter : Il componente HttpKernel
262 ) L'evento kernel.view Scopi tipici: Trasformare un valore diverso da Response restituito da un controllore in una Response Tabella informativa sugli eventi del kernel Se il controllore non restituisce un oggetto Response, iol kernel distribuisce un altro evento, kernel.view. Il compito di un ascoltatore di tale evento è di usare il valore restituito dal controllore (p.e. un array di dati o un oggetto) per creare una Response. Questo può essere utile se si vuole usare un livello "vista": invece di restituire una Response dal controllore, si restituiscono dati che rappresentano la pagina. Un ascoltatore di questo evento può quindi usare tali dati per creare una Response che sia nel formato corretto (p.e. HTML, JSON, ecc.). A questo punto, ne nessun ascoltatore imposta una risposta sull'evento, viene lanciata un'eccezione: o il controllore o uno degli ascoltatori della vista devono sempre restituire una Response. Quando si imposta una risposta per l'evento kernel.request, la propagazione si ferma. Questo vuol dire che ascoltatori con priorità inferiore non saranno eseguiti. kernel.view in the Symfony Framework Non c'è un ascoltatore predefinito nel framework Symfony per l'evento kernel.view. Tuttavia, un bundle del nucleo, SensioFrameworkExtraBundle, aggiunge un ascoltatore a questo evento. Se un controllore restituisce un array, e se si inserisce in cima al controllore, questo ascoltatore rende un template, passa l'array restituita dal controllore a tale template e crea una Response con il contenuto restituito da tale template. Inoltre, un bundle popolare della comunità, FOSRestBundle, implementa un ascoltatore di questo evento, con lo scopo di fornire un robusto livello vista, capace di usare un singolo controllore per restituire molti differenti tipi di risposta (p.e. HTML, JSON, XML, ecc) generated on August, 0 Chapter : Il componente HttpKernel
263 ) L'evento kernel.response Scopi tipici: Modificare l'oggetto Response subito prima che sia inviato Tabella informativa sugli eventi del kernel Lo scopo finale del kernel è trasformare una Request in una Response. La Response può essere creata durante l'evento kernel.request, restituita dal controllore oppure restituita da uno degli ascoltatori dell'evento kernel.view. Indipendentemente da chi abbia creato la Response, un altro evento, kernel.response, viene distribuito subito dopo. Un tipico ascoltatore di questo evento modificherà l'oggetto Response in qualche modo, modificando header, aggiungendo cookie o anche cambiando il contenuto della Response stessa (p.e. iniettando del codice JavaScript prima della chiusura del tag </body> di una risposta HTML). Dopo la distribuzione di questo evento, l'oggetto finale Response viene restituito da handle() 0. Nel caso d'uso più tipico, si può quindi richiamare il metodo send(), che invia gli header e stampa il contenuto della Response. kernel.response in the Symfony Framework Ci sono molti altri ascoltatori minori di questo evento nel framework Symfony, la maggior parte dei quali modifica la risposta in qualche modo. Per esempio, WebDebugToolbarListener inietta del codice JavaScript in fondo alla pagina in ambiente dev, per mostrare la barra di debug del web. Un altro ascoltatore, ContextListener, serializza le informazioni sull'utente corrente nella sessione, in modo che possano essere ricaricate alla richiesta successiva. ) L'evento kernel.terminate Scopi tipici: Eseguire qualche azione "pesante" dopo che la risposta sia stata inviata all'utente Tabella informativa sugli eventi del kernel L'evento finale processato da HttpKernel è kernel.terminate, che è unico, perché avviene dopo il metodo HttpKernel::handle e quindi dopo che la risposta è stata inviata all'utente. Ripreso da sopra, il codice usato dal kernel finisce in questo modo: Listing - // mostra il contenuto e invia gli header $response->send(); // lancia l'evento kernel.terminate $kernel->terminate($request, $response); Come si può vedere, richiamando $kernel->terminate dopo l'invio della risposta, si lancerà l'evento kernel.terminate, in cui si possono eseguire alcune azioni che potrebbero essere state rimandate, per poter restituire la risposta nel modo più veloce possibile al client (p.e. invio di ). Internamente, HttpKernel fa uso della funzione fastcgi_finish_request di PHP. Questo vuole dire che, al momento, solo le API del server PHP FPM sono in grado di inviare al cliente una risposta mentre il processo PHP del server esegue ancora alcuni compiti. Con le API di ogni altro server, gli ascoltatori di kernel.terminate sono comunque eseguiti, ma la risposta non viene inviata al cliente finché non sono tutti completati generated on August, 0 Chapter : Il componente HttpKernel
264 L'uso dell'evento kernel.terminate è facoltativo e va limitato al caso in cui il kernel implementi TerminableInterface. kernel.terminate in the Symfony Framework Se si usa SwiftmailerBundle con Symfony e si usa lo spool memory, viene attivato SenderListener, che invia effettivamente le pianificate per essere inviate durante la richiesta. Gestire le eccezioni:: l'evento kernel.exception Scopi tipici: Gestire alcuni tipi di eccezioni e creare un'appropriata Response da restituire per l'eccezione Tabella informativa sugli eventi del kernel Se a un certo punto in HttpKernel::handle viene lanciata un'eccezione, viene lanciato un altro evento, kernel.exception. Internamente, il corpo della funzione handle viene avvolto da un blocco try-catch. Quando viene lanciata un'eccezione, l'evento kernel.exception viene distribuito, in modo che il proprio sistema possa in qualche modo rispondere all'eccezione. A ogni ascoltatore di questo evento viene passato un oggetto GetResponseForExceptionEvent, che si può usare per accedere all'eccezione originale, tramite il metodo getexception(). Un tipico ascoltatore di questo evento verificherà un certo tipo di eccezione e creerà un appropriata Response di errore. Per esempio, per generare una pagina 0, si potrebbe lanciare uno speciale tipo di eccezione e quindi aggiungere un ascoltatore a tale evento, che cerchi l'eccezione e crei e restituisca una Response 0. In generated on August, 0 Chapter : Il componente HttpKernel
265 effetti, il componente HttpKernel dispone di un ExceptionListener 0, che, se usato, farà questo e anche di più in modo predefinito (si vedano dettagli più avanti). Quando si imposta una risposta per l'evento kernel.request, la propagazione si ferma. Questo vuol dire che ascoltatori con priorità inferiore non saranno eseguiti. kernel.exception in the Symfony Framework Ci sono due ascoltatori principali di kernel.exception quando si usa il framework Symfony. ExceptionListener in HttpKernel Il primo fa parte del componente HttpKernel e si chiama ExceptionListener. L'ascoltatore ha diversi scopi:. L'eccezione lanciata è convertita in un oggetto FlattenException, che contiene tutte le informazioni sulla richiesta, ma che possa essere stampata e serializzata.. Se l'eccezione originale implementa HttpExceptionInterface, allora sono richiamati getstatuscode e getheaders sull'eccezione e usati per popolare gli header e il codice di stato dell'oggetto FlattenException. L'idea è che siano usati nel passo successivo, quando si crea la risposta finale.. Un controllore viene eseguito e gli viene passata l'eccezione appiattita. Il controllore esatto da rendere viene passato come parametro del costruttore a questo ascoltatore. Questo controllore restituirà la Response finale per questa pagina di errore. ExceptionListener in Security L'altro importante ascoltatore è ExceptionListener. Lo scopo di questo ascoltatore è gestire le eccezioni di sicurezza e, quando appropriato, aiutare l'utente ad autenticarsi (p.e. rinviando alla pagina di login). Creare un ascoltatore di eventi Come abbiamo visto, si possono creare e attaccare ascoltatori di eventi a qualsiasi evento distribuito durante il ciclo HttpKernel::handle. Un tipico ascoltatore è una classe PHP con un metodo da eseguire, ma può essere qualsiasi cosa. Per maggiori informazioni su come creare e attaccare ascoltatori di eventi, si veda Il componente EventDispatcher. Il nome di ogni evento del kernel è definito come costante della classe KernelEvents. Inoltre, a ogni ascoltatore di evento viene passato un singolo parametro, che è una sotto-classe di KernelEvent. Questo oggetto contiene informazioni sullo stato attuale del sistema e ogni vento ha il suo oggetto evento: Nome Costante KernelEvents Parametro passato all'ascoltatore kernel.request KernelEvents::REQUEST GetResponseEvent kernel.controller KernelEvents::CONTROLLER FilterControllerEvent generated on August, 0 Chapter : Il componente HttpKernel
266 Nome Costante KernelEvents Parametro passato all'ascoltatore kernel.view KernelEvents::VIEW GetResponseForControllerResultEvent kernel.response KernelEvents::RESPONSE FilterResponseEvent 0 kernel.finish_request KernelEvents::FINISH_REQUEST FinishRequestEvent kernel.terminate KernelEvents::TERMINATE PostResponseEvent kernel.exception KernelEvents::EXCEPTION GetResponseForExceptionEvent Un esempio funzionante Quando si usa il componente HttpKernel, si è liberi di connettere qualsiasi ascoltatore agli eventi del nucleo e di usare qualsiasi risolutore di controllori che implementi ControllerResolverInterface. Tuttavia, il componente HttpKernel dispone di alcuni ascoltatori predefiniti e di un ControllerResolver predefinito, utilizzabili per creare un esempio funzionante: Listing use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\HttpKernel; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\HttpKernel\Controller\ControllerResolver; use Symfony\Component\HttpKernel\EventListener\RouterListener; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; $routes = new RouteCollection(); $routes->add('hello', new Route('/hello/name', array( '_controller' => function (Request $request) return new Response( sprintf("ciao %s", $request->get('name')) ); ) )); $request = Request::createFromGlobals(); $matcher = new UrlMatcher($routes, new RequestContext()); $dispatcher = new EventDispatcher(); $dispatcher->addsubscriber(new RouterListener($matcher)); $resolver = new ControllerResolver(); $kernel = new HttpKernel($dispatcher, $resolver); $response = $kernel->handle($request); $response->send(); generated on August, 0 Chapter : Il componente HttpKernel
267 $kernel->terminate($request, $response); Sotto-richieste Oltre alla richiesta "principale", inviata a HttpKernel::handle, si possono anche inviare delle cosiddette "sotto-richieste". Una sotto-richiesta assomiglia e si comporta come ogni altra richiesta, ma tipicamente serve a rendere solo una breve porzione di una pagina, invece di una pagina completa. Solitamente si fanno sotto-richieste da un controllore (o forse da dentro a un template, che viene reso da un controllore). Per eseguire una sotto-richiesta, usare HttpKernel::handle, ma cambiando il secondo parametro, come segue: Listing - 0 use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\HttpKernelInterface; // creare qualche altra richiesta a mano, come necessario $request = new Request(); // per resempio, si potrebbe impostare a mano _controller $request->attributes->add('_controller', '...'); $response = $kernel->handle($request, HttpKernelInterface::SUB_REQUEST); // fare qualcosa con questa risposta Questo crea un altro ciclo richiesta-risposta, in cui la nuova Request è trasformata in una Response. L'unica differenza interna è che alcuni ascoltatori (p.e. security) possono intervenire solo per la richiesta principale. A ogni ascoltatore viene passata una sotto-classe di KernelEvent, il cui metodo getrequesttype() può essere usato per capire se la richiesta corrente sia una richiesta principale o una sotto-richiesta generated on August, 0 Chapter : Il componente HttpKernel
268 Per esempio, un ascoltatore che deve agire solo sulla richiesta principale può assomigliare a questo: Listing - 0 use Symfony\Component\HttpKernel\HttpKernelInterface; public function onkernelrequest(getresponseevent $event) if (!$event->ismasterrequest()) return; generated on August, 0 Chapter : Il componente HttpKernel
269 Chapter Il componente Intl Un rimpiazzo PHP per l'estensione intl C, che fornisce anche accesso ai dati di localizzazione della libreria ICU. New in version.: Il componente Intl è stato aggiunto in Symfony.. Nelle precedenti versioni di Symfony, va usato il componente Locale al suo posto. Il rimpiazzo è limitato al locale "en". Se si vogliono usare altri locale, si dovrebbe invece installare l'estensione intl. Installazione Si può installare il componente in due modi: Usando il repository Git ufficiale ( ); Tramite Composer (symfony/intl su Packagist ). Se si installa il componente tramite Composer, saranno fornite automaticamente le seguenti classi e funzioni dell'estensione intl, nel caso in cui l'estensione intl stessa non sia caricata: Collator IntlDateFormatter Locale generated on August, 0 Chapter : Il componente Intl
270 NumberFormatter intl_error_name 0 intl_is_failure intl_get_error_code intl_get_error_message Quando l'estensione intl non è disponibile, sono usate le seguenti classi, per sostituire quelle di intl: Collator IntlDateFormatter Locale NumberFormatter IntlGlobals Composer espone automaticamente tali classi nello spazio dei nomi globale. Se non si usa Composer, ma il componente ClassLoader di Symfony, occorre esporre a mano le classi, aggiungendo le linee seguenti al proprio autoloader: Listing - if (!function_exists('intl_is_failure')) require '/percorso/delle/funzioni/icu/funzioni.php'; $loader->registerprefixfallback('/percorso/delle/funzioni/icu'); generated on August, 0 Chapter : Il componente Intl 0
271 ICU e problemi di deploy L'estensione intl usa internamente la libreria ICU per ottenere dati localizzati, come formati numerici nelle varie lingue, nomi di paesi, eccetera. Per rendere disponibili tali dati alle librerie utente di PHP, Symfony ne possiede una copia nel componente ICU 0. A seconda della versione di ICU compilata nell'estensione intl, occorre installare una versione corrispondente del componente. Sembra complicato, ma di solito Composer lo fa in modo automatico:.0.*: se l'estensione intl non è disponibile..*: se intl è compilato con ICU.0 o successivi..*: se intl è compilato con ICU. o successivi Queste versioni sono importanti se di esegue il deploy su un server con una versione di ICU precedente a quella della macchina di sviluppo, perché il deploy fallirà se la macchina di sviluppo è compilata con ICU. o successivi, ma il server compilato con una versione di ICU precedente alla.; l'estensione intl è disponibile sulla macchina di sviluppo, ma non sul server. Per esempio, ipotizziamo che la macchina di sviluppo abbia ICU. e il server ICU.. Quando si esegue php composer.phar update sulla macchina di sviluppo, sarà installata la versione..* del componente ICU. Ma, dopo il deploy dell'applicazione, php composer.phar install fallirà con il seguente errore: Listing - $ php composer.phar install Loading composer repositories with package information Installing dependencies from lock file Your requirements could not be resolved to an installable set of packages. Problem - symfony/icu..x requires lib-icu >=. -> the requested linked library icu has the wrong version installed or is missing from your system, make sure to have the extension providing it. L'errore dice che la versione richiesta del componente ICU, la., non è compatibile con la versione. di ICU di PHP. Una possibile soluzione consiste nell'eseguire php composer.phar update invece di php composer.phar install. Ma si raccomanda caldamente di non fare in questo modo. Il comando update installerà le versioni più recenti di ogni dipendenza di Composer nel server di produzione, il che potrebbe rompere l'applicazione. Una soluzione migliore consiste nel sistemare composer.json, inserendo la versione richiesta dal server di produzione. Innanzitutto, determinare la versione ICU sul server: Listing - $ php -i grep ICU ICU version =>.. Quindi modificare il componente ICU in composer.json, inserendo una versione corrispondente: Listing - "require: "symfony/icu": "..*" Impostare la versione a ".0.*" se il server non ha l'estensione intl installata; generated on August, 0 Chapter : Il componente Intl
272 "..*" se il server è compilato con ICU. o precedenti. Infine, eseguire php composer.phar update symfony/icu sulla macchina di sviluppo, testare estensivamente e fare un nuovo deploy. L'installazione delle dipendenze ora avrà successo. Scrivere e leggere i bundle delle risorse La classe ResourceBundle non è attualmente supportata da questo componente. Invece, sono inclusi un insieme di lettori e scrittori, per leggere e scrivere array (o oggetti simili ad array) da/a file dei bundle delle risorse. Sono supportate le classi seguenti: TextBundleWriter PhpBundleWriter BinaryBundleReader PhpBundleReader BufferedBundleReader StructuredBundleReader Chi fosse interessato all'uso di tali classi può continuare la lettura. Altrimenti, si può saltare la sezione e andare ad Accesso ai dati ICU. TextBundleWriter TextBundleWriter scrive un array o un oggetto simile ad array in un bundle di risorse in testo. Il file.txt risultante può essere convertito in un file binario.res con la classe BundleCompiler : Listing - 0 use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter; use Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompiler; $writer = new TextBundleWriter(); $writer->write('/percorso/del/bundle', 'en', array( 'Data' => array( 'voce', 'voce', ), )); $compiler = new BundleCompiler(); $compiler->compile('/percorso/del/bundle', '/percorso/del/bundle/binario'); Il comando "genrb" della classe BundleCompiler deve essere disponibile. Se il comando si trova in una posizione non standard, si può passare il suo percorso al costruttore di BundleCompiler. PhpBundleWriter PhpBundleWriter scrive un array o un oggetto simile ad array in un bundle di risorse.php: generated on August, 0 Chapter : Il componente Intl
273 Listing - 0 use Symfony\Component\Intl\ResourceBundle\Writer\PhpBundleWriter; $writer = new PhpBundleWriter(); $writer->write('/percorso/del/bundle', 'en', array( 'Data' => array( 'voce', 'voce', ), )); BinaryBundleReader BinaryBundleReader legge file binari e restituisce un array o un oggetto simile ad array. La classe attualmente funziona solo con l'estensione intl installata: Listing - use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; $reader = new BinaryBundleReader(); $data = $reader->read('/percorso/del/bundle', 'en'); echo $data['data']['voce']; PhpBundleReader PhpBundleReader legge file.php e restituisce un array o un oggetto simile ad array: Listing - use Symfony\Component\Intl\ResourceBundle\Reader\PhpBundleReader; $reader = new PhpBundleReader(); $data = $reader->read('/percorso/del/bundle', 'en'); echo $data['data']['voce']; BufferedBundleReader BufferedBundleReader 0 avvolge un altro lettore, ma mantiene le ultime N letture in un buffer, dove N è la dimensione passata al costruttore: Listing - use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; use Symfony\Component\Intl\ResourceBundle\Reader\BufferedBundleReader; $reader = new BufferedBundleReader(new BinaryBundleReader(), 0); // legge il file $data = $reader->read('/percorso/del/bundle', 'en'); generated on August, 0 Chapter : Il componente Intl
274 0 // restituisce dati dal buffer $data = $reader->read('/percorso/del/bundle', 'en'); // legge il file $data = $reader->read('/percorso/del/bundle', 'fr'); StructuredBundleReader StructuredBundleReader avvolge un altro lettore e offre un metodo readentry() per leggere un elemento del bundle risorsa senza doversi preoccupare se le chiavi dell'array siano impostate o meno. Se un percorso non può essere risolto, viene restituito null`: Listing -0 0 use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; use Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReader; $reader = new StructuredBundleReader(new BinaryBundleReader()); $data = $reader->read('/percorso/del/bundle', 'en'); // provoca un errore se la chiave "Data" non esiste echo $data['data']['entry']; // restituice null se la chiave "Data" non esiste echo $reader->readentry('/percorso/del/bundle', 'en', array('data', 'entry')); Inoltre, il metodo readentry() risolve i locale a cascata. Per esempio, il locale a cascata di "en_gb" è "en". Per elementi a valore singolo (stringhe, numeri ecc.), l'elemento sarà letto dal locale a cascata, se non trovato nel locale specifico. Per elementi a valori multipli (array), il valore del locale specifico e di quello a cascata saranno fusi. Per evitare tale comportamento, si può impostare il parametro $fallback a false: Listing - echo $reader->readentry( '/percorso/del/bundle', 'en', array('data', 'entry'), false ); Accesso ai dati ICU I dati ICU si trovano in vari "bundle risorsa". Si può accedere a un wrapper PHP di tali bundle, tramite la classe statica Intl. Al momento, sono supportati i dati seguenti: Nomi di lingue e di script Nomi di paesi Locale Valute generated on August, 0 Chapter : Il componente Intl
275 Nomi di lingue e di script Le traduzioni di nomi di lingue e di script si trovano nel bundle "language": Listing - 0 use Symfony\Component\Intl\Intl; \Locale::setDefault('en'); $languages = Intl::getLanguageBundle()->getLanguageNames(); // => array('ab' => 'Abkhazian',...) $language = Intl::getLanguageBundle()->getLanguageName('de'); // => 'German' $language = Intl::getLanguageBundle()->getLanguageName('de', 'AT'); // => 'Austrian German' $scripts = Intl::getLanguageBundle()->getScriptNames(); // => array('arab' => 'Arabic',...) $script = Intl::getLanguageBundle()->getScriptName('Hans'); // => 'Simplified' Tutti i metodi accettano il locale come ultimo parametro, opzionale, con valore predefinito il locale predefinito: Listing - $languages = Intl::getLanguageBundle()->getLanguageNames('de'); // => array('ab' => 'Abchasisch',...) Nomi di paesi Le traduzioni di nomi di paesi si trovano nel bundle "region": Listing - use Symfony\Component\Intl\Intl; \Locale::setDefault('en'); $countries = Intl::getRegionBundle()->getCountryNames(); // => array('af' => 'Afghanistan',...) $country = Intl::getRegionBundle()->getCountryName('GB'); // => 'United Kingdom' Tutti i metodi accettano il locale come ultimo parametro, opzionale, con valore predefinito il locale predefinito: Listing - $countries = Intl::getRegionBundle()->getCountryNames('de'); // => array('af' => 'Afghanistan',...) Locale Le traduzioni di nomi di locale si trovano nel bundle "locale": Listing - generated on August, 0 Chapter : Il componente Intl
276 use Symfony\Component\Intl\Intl; \Locale::setDefault('en'); $locales = Intl::getLocaleBundle()->getLocaleNames(); // => array('af' => 'Afrikaans',...) $locale = Intl::getLocaleBundle()->getLocaleName('zh_Hans_MO'); // => 'Chinese (Simplified, Macau SAR China)' Tutti i metodi accettano il locale come ultimo parametro, opzionale, con valore predefinito il locale predefinito: Listing - $locales = Intl::getLocaleBundle()->getLocaleNames('de'); // => array('af' => 'Afrikaans',...) Valute Le traduzioni di nomi di valute e altre informazioni relative alle valute si trovano nel bundle "currency": Listing - 0 use Symfony\Component\Intl\Intl; \Locale::setDefault('en'); $currencies = Intl::getCurrencyBundle()->getCurrencyNames(); // => array('afn' => 'Afghan Afghani',...) $currency = Intl::getCurrencyBundle()->getCurrencyName('INR'); // => 'Indian Rupee' $symbol = Intl::getCurrencyBundle()->getCurrencySymbol('INR'); // => '?' $fractiondigits = Intl::getCurrencyBundle()->getFractionDigits('INR'); // => $roundingincrement = Intl::getCurrencyBundle()->getRoundingIncrement('INR'); // => 0 Tutti i metodi (tranne getfractiondigits() e getroundingincrement() ) accettano il locale come ultimo parametro, opzionale, con valore predefinito il locale predefinito: Listing - $currencies = Intl::getCurrencyBundle()->getCurrencyNames('de'); // => array('afn' => 'Afghanische Afghani',...) Questo è tutto quello che occorre sapere, per ora. Buon divertimento con il codice!. generated on August, 0 Chapter : Il componente Intl
277 Chapter Il componente OptionsResolver Il Componente OptionsResolver aiuta a configurare gli oggetti con un array di opzioni. Esso supporta valori predefiniti, opzioni con vincoli e opzioni pigre. Installazione È possibile installare il componente in due modi differenti: installarlo tramite Composer (symfony/options-resolver su Packagist ) utilizzare il repository ufficiale Git ( ) Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Utilizzo Si immagini di avere una classe Mailer che ha opzioni: host e password. Queste opzioni stanno per essere gestite dal Componente OptionsResolver. Innanzitutto, creare la classe Mailer: Listing - class Mailer protected $options; public function construct(array $options = array()). generated on August, 0 Chapter : Il componente OptionsResolver
278 Si potrebbe impostare il valore di $options direttamente nella proprietà. Invece, utilizzare la classe OptionsResolver e lasciare che essa risolva le opzioni tramite la chiamata al metodo resolve(). I vantaggi di operare in questo modo saranno più ovvi andando avanti: Listing - use Symfony\Component\OptionsResolver\OptionsResolver; public function construct(array $options = array()) $resolver = new OptionsResolver(); $this->options = $resolver->resolve($options); La proprietà $options è ora un array ben definito, con tutte le opzioni risolte rese disponibili: Listing - public function sendmail($from, $to) $mail =...; $mail->sethost($this->options['host']); $mail->setusername($this->options['username']); $mail->setpassword($this->options['password']); Configurare OptionsResolver Adesso, si provi a utilizzare effettivamente la classe: Listing - $mailer = new Mailer(array( 'host' => 'smtp.example.org', 'username' => 'user', 'password' => 'pa$$word', )); In questo momento, si riceverà una InvalidOptionsException, la quale informa che le opzioni host e password non esistono. Questo perché è necessario configurare prima l'optionsresolver, in modo che sappia quali opzioni devono essere risolte. Per controllare se un'opzione esiste, si può utilizzare la funzione isknown() generated on August, 0 Chapter : Il componente OptionsResolver
279 Una buona pratica è porre la configurazione in un metodo (per esempio setdefaultoptions). Il metodo viene invocato nel costruttore per configurare la classe OptionsResolver: Listing use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class Mailer protected $options; public function construct(array $options = array()) $resolver = new OptionsResolver(); $this->configureoptions($resolver); $this->options = $resolver->resolve($options); protected function configureoptions(optionsresolverinterface $resolver) configura il resolver, come si apprendererà nelle // sezioni successive Impostare valori predefiniti Spesso le opzioni hanno un valore predefinito. Lo si può configurare richiamando setdefaults() : Listing - protected function setdefaultoptions(optionsresolverinterface $resolver) $resolver->setdefaults(array( 'username' => 'root', )); Questo aggiunge un'opzione username con un valore predefinito root. Se l'utente passerà un'opzione username, il suo valore sovrascriverà quello predefinito. Non occorre configurare username come opzione facoltativa. Opzioni Obbligatorie Supponiamo che l'opzione host sia obbligatoria: la classe non può funzionare senza di essa. Si possono impostare le opzioni obbligatorie invocando setrequired() : Listing - protected function setdefaultoptions(optionsresolverinterface $resolver). generated on August, 0 Chapter : Il componente OptionsResolver
280 $resolver->setrequired(array('host')); A questo punto è possible usare la classe senza errori: Listing - $mailer = new Mailer(array( 'host' => 'smtp.example.org', )); echo $mailer->gethost(); // 'smtp.example.org' Se un'opzione obbligatoria non viene passata, sarà sollevata una MissingOptionsException. Per determinare se un'opzione è obbligatoria, si può usare il metodo isrequired() 0. Opzioni Facoltative A volte un'opzione può essere facoltativa (per esempio l'opzione password nella classe Mailer). È possibile configurare queste opzioni invocando setoptional() : Listing - protected function setdefaultoptions(optionsresolverinterface $resolver) $resolver->setoptional(array('password')); Le opzioni con valori predefiniti sono sempre impostate come facoltative. Se si imposta un'opzione come facoltativa, non si può essere sicuri che sia compresa o meno nell'array. Occorre verificarne l'esistenza prima di poterla usare. Per evitare di doverla verificare ogni volta, si può anche impostare un valore predefinito di null, usando il metodo setdefaults() (vedere Impostare valori predefiniti), il che vuol dire che l'elemento esisterà sempre nell'array, ma con un valore predefinito di null. Valori predefiniti che dipendono da altre Opzioni Supponiamo di aggiungere un'opzione port alla classe Mailer, il cui valore predefinito è indovinato sulla base dell'host. Lo si può fare facilmente, usando una Closure come valore predefinito: Listing -0 use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; generated on August, 0 Chapter : Il componente OptionsResolver 0
281 0 protected function setdefaultoptions(optionsresolverinterface $resolver) $resolver->setdefaults(array( 'encryption' => null, 'port' => function (Options $options) if ('ssl' === $options['encryption']) return ; ));, return ; La classe Options implementa ArrayAccess, Iterator e Countable. Ciò vuol dire che la si può gestire come un normale array che contenga le opzioni. Il primo parametro della Closure deve essere di tipo Options, altrimenti sarà considerata come il valore. Sovrascrivere i valori predefiniti Un valore predefinito, impostato in precedenza, può essere sovrascritto invocando di nuovo setdefaults(). Se si usa una closure come nuovo valore, riceverà due parametri: $options: un'istanza di Options, con tutti i valori predefiniti $previousvalue: il precedente valore predefinito Listing - 0 use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; protected function setdefaultoptions(optionsresolverinterface $resolver) $resolver->setdefaults(array( 'encryption' => 'ssl', 'host' => 'localhost', )); $resolver->setdefaults(array( 'encryption' => 'tls', // sovrascrittura semplice 'host' => function (Options $options, $previousvalue) generated on August, 0 Chapter : Il componente OptionsResolver
282 0 ));, return 'localhost' == $previousvalue? '.0.0.' : $previousvalue; Se il precedente valore predefinito è calcolato da una closure impegnativa e non si ha bisogno di accedervi, si può usare invece il metodo replacedefaults(). Questo metodo agisce come setdefaults, ma cancella semplicemente il valore precedente, per migliorare le prestazioni. Questo vuol dire che il precedente valore predefinito non è disponibile se si sovrascrive con un'altra closure: Listing use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; protected function setdefaultoptions(optionsresolverinterface $resolver) $resolver->setdefaults(array( 'encryption' => 'ssl', 'heavy' => function (Options $options) // dei calcoli pesanti per creare $result, )); return $result; $resolver->replacedefaults(array( 'encryption' => 'tls', // sovrascrittura semplice 'heavy' => function (Options $options) // $previousvalue non disponibile, )); return $someotherresult; Le chiavi di opzioni esistenti non menzionate durante la sovrascrittura saranno preseervate. Configurare i Valori consentiti Non tutti i valori sono validi per le opzioni. Supponiamo che la classe Mailer abbia un'opzione transport, che può valere solo sendmail, mail o smtp. È possibile configurare questi valori consentiti, invocando setallowedvalues() : Listing generated on August, 0 Chapter : Il componente OptionsResolver
283 protected function setdefaultoptions(optionsresolverinterface $resolver) $resolver->setallowedvalues(array( 'encryption' => array(null, 'ssl', 'tls'), )); Esiste anche un metodo addallowedvalues() 0, che è possibile utilizzare se si vuole aggiungere un valore consentito al precedente set di valori consentiti. New in version.: Il supporto per i callback per i valori consentiti è stato introdotto in Symfony.. Se si ha bisogno di aggiungere un po' di logica al processo di validazione del valore, si può passare un callable come valore consentito: Listing - 0 protected function setdefaultoptions(optionsresolverinterface $resolver) $resolver->setallowedvalues(array( 'transport' => function($value) return false!== strpos($value, 'mail');, )); Si noti che questo utilizzo insieme ad addallowedvalues non funzionerà. Configurare i tipi consentiti È possibile anche specificare i valori consentiti. Per esempio, l'opzione port può essere qualsiasi cosa, ma deve essere un intero. È possibile configurare questi tipi invocando setallowedtypes() : Listing - protected function setdefaultoptions(optionsresolverinterface $resolver) $resolver->setallowedtypes(array( 'port' => 'integer', )); I possibili tipi sono quelli associati alle funzioni php is_* o al nome della classe. È possibile passare anche un array di tipi come valore. Per esempio, array('null', 'string') consente che port sia nullo o una stringa generated on August, 0 Chapter : Il componente OptionsResolver
284 Esiste anche un metodo addallowedtypes(), che può essere utilizzato per aggiungere un tipo consentito a quelli precedentemente indicati. Normalizzare le Opzioni Alcuni valori devono essere normalizzati prima che possano essere usati. Per esempio, firstname dovrebbe sempre iniziare con una lettera maiuscola. Per fare ciò, si posso scrivere dei normalizzatori. Queste Closure saranno eseguite dopo che tutte le opzioni sono state passate e ritornano il valore normalizzato. I normalizzatori possono essere configurati invocando setnormalizers() : Listing - 0 protected function setdefaultoptions(optionsresolverinterface $resolver) $resolver->setnormalizers(array( 'host' => function (Options $options, $value) if (' substr($value, 0, )) $value = ' ));, return $value; Si può notare che la closure riceve un parametro $options. Qualche volta, è necessario utilizzare altre opzioni per normalizzare: Listing - 0 protected function setdefaultoptions(optionsresolverinterface $resolver) $resolver->setnormalizers(array( 'host' => function (Options $options, $value) if (!in_array(substr($value, 0, ), array(' ' if ($options['ssl']) $value = ' else $value = ' ));, return $value;. generated on August, 0 Chapter : Il componente OptionsResolver
285 Chapter Il componente Process Il componente Process esegue i comandi nei sotto-processi. Installazione Si può installare il componente in due modi: Installarlo tramite Composer (symfony/process su Packagist ); Usare il repository ufficiale su Git ( ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Uso La classe Process consente di eseguire un comando in un sotto-processo: Listing - use Symfony\Component\Process\Process; $process = new Process('ls -lsa'); $process->run(); // eseguito deopo la fine del comando if (!$process->issuccessful()) throw new \RuntimeException($process->getErrorOutput()); generated on August, 0 Chapter : Il componente Process
286 0 print $process->getoutput(); Il metodo si prende cura delle sottili differenze tra le varie piattaforme, durante l'esecuzione del comando. Il metodo getoutput() restituisce sempre l'intero contenuto dell'output standard del comando, mentre geterroroutput() restituisce l'intero contenuto dell'output di errore. In alternativa, i metodi getincrementaloutput() e getincrementalerroroutput() restituiscono i nuovi output dall'ultima chiamata. New in version.: I metodi flushoutput() e flusherroroutput() sono stati aggiunti in Symfony.. Il metodo flushoutput() aggiorna il contenuto dell'output e flusherroroutput() aggiorna il contenuto degli errori. Output del processo in tempo reale Quando si esegue un comando che gira a lungo (come la sincronizzazione di file con un server remoto), si può dare un feedback all'utente finale in tempo reale, passando una funzione anonima al metodo run() : Listing - 0 use Symfony\Component\Process\Process; $process = new Process('ls -lsa'); $process->run(function ($type, $buffer) if (Process::ERR === $type) echo 'ERR > '.$buffer; else echo 'OUT > '.$buffer; ); Esecuzione asincrona dei processi Si può anche iniziare il sotto-processo e lasciarlo girare in modo asincrono, recuperando l'output e lo stato nel processo principale, quando occorre. Usare il metodo start() per iniziare un processo asincrono, il metodo isrunning() 0 per verificare che il processo sia finito e il metodo getoutput() per ottenere l'output: Listing - $process = new Process('ls -lsa'); $process->start(); while ($process->isrunning()) // aspetta che il processo finisca generated on August, 0 Chapter : Il componente Process
287 echo $process->getoutput(); Si può anche aspettare che un processo finisca, se è stato fatto partire in modo asincrono e si sta facendo altro: Listing - 0 $process = new Process('ls -lsa'); $process->start(); fare altre cose $process->wait(function ($type, $buffer) if (Process::ERR === $type) echo 'ERR > '.$buffer; else echo 'OUT > '.$buffer; ); Il metodo wait() è bloccante, il che vuol dire che il codice si fermerà a quella linea, finché il processo esterno non sarà finito. Fermare un processo New in version.: Il parametro signal del metodo stop è stato aggiunto in Symfony.. Ogni processo asincrono può essere fermato in qualsiasi momento, con il metodo stop(). Questo metodo accetta due parametri: una scadenza e un segnale. Una volta raggiunta la scadenza, il segnale viene inviato al processo in esecuzione. Il segnale predefinito inviato al processo è SIGKILL. Si legga la documentazione sui segnali per approfondire la gestione dei segnali nel componente Process: Listing - $process = new Process('ls -lsa'); $process->start(); fare altre cose $process->stop(, SIGINT); Eseguire codice PHP in isolamento Se si vuole eseguire del codice PHP in isolamento, usare invece PhpProcess: Listing - use Symfony\Component\Process\PhpProcess; $process = new PhpProcess(<<<EOF <?php echo 'Ciao mondo';?>. generated on August, 0 Chapter : Il componente Process
288 EOF ); $process->run(); Per far funzionare meglio il proprio codice su tutte le piattaforme, potrebbe essere preferibile usare la classe ProcessBuilder : Listing - use Symfony\Component\Process\ProcessBuilder; $builder = new ProcessBuilder(array('ls', '-lsa')); $builder->getprocess()->run(); New in version.: Il metodo ProcessBuilder::setPrefix è stato aggiunto in Symfony.. Se si sta costruendo un driver binario, si può usare il metodo setprefix() per prefissare tutti i comandi del processo generato. L'esempio seguente genererà due comandi di processo per un adattatore binario di tar: Listing - 0 use Symfony\Component\Process\ProcessBuilder; $builder = new ProcessBuilder(); $builder->setprefix('/usr/bin/tar'); // '/usr/bin/tar' '--list' '--file=archive.tar.gz' echo $builder ->setarguments(array('--list', '--file=archive.tar.gz')) ->getprocess() ->getcommandline(); // '/usr/bin/tar' '-xzf' 'archive.tar.gz' echo $builder ->setarguments(array('-xzf', 'archive.tar.gz')) ->getprocess() ->getcommandline(); Scadenza del processo Si può limitare il tempo a disposizione di un processo per essere completato, impostando una scadenza (in secondi): Listing - use Symfony\Component\Process\Process; $process = new Process('ls -lsa'); $process->settimeout(00); $process->run(); Se questo tempo viene raggiunto, viene lanciata una RuntimeException. Per comandi che richiedono molto tempo, è responsabilità dello sviluppatore controllare il timeout a intervalli regolari: generated on August, 0 Chapter : Il componente Process
289 Listing -0 0 $process->settimeout(00); $process->start(); while ($condition) // verifica se è stato raggiunto il timeout $process->checktimeout(); usleep(00000); Scadenza del processo inattivo New in version.: Il metodo setidletimeout() è stato aggiunto in Symfony.. Contrariamente alla scadenza vista nel paragrafo precedente, la scadenza inattiva considera solo il tempo dall'ultimo output prodotto dal processo: Listing - use Symfony\Component\Process\Process; $process = new Process('qualcosa-con-runtime-variabile'); $process->settimeout(00); $process->setidletimeout(0); $process->run(); In questo caso, si considera scaduto un processo se il tempo totale eccede 00 secondi o se il processo non produce output per 0 secondi. Segnali di processo New in version.: Il metodo signal è stato aggiunto in Symfony.. Durante l'esecuzione di un programma asincrono, si possono inviare segnali posix, con il metodo signal() : Listing - use Symfony\Component\Process\Process; $process = new Process('find / -name "rabbit"'); $process->start(); // invierà un SIGKILL al processo $process->signal(sigkill);. generated on August, 0 Chapter : Il componente Process
290 A causa di alcune limitazioni in PHP, se si usano segnali con il componente Process, potrebbe essere necessario prefissare i comandi con exec 0. Si leggano la issue # di Symfony e il bug # di PHP per capire perché questo accada. I segnali POSIX non sono disponibili su piattaforme Windows, si faccia riferimento alla documentazione di PHP per i segnali disponibili. Pid del processo New in version.: Il metodo getpid è stato aggiunto in Symfony.. Si può avere accesso al pid di un processo in esecuzione, con il metodo getpid(). Listing - use Symfony\Component\Process\Process; $process = new Process('/usr/bin/php worker.php'); $process->start(); $pid = $process->getpid(); A causa di alcune limitazioni in PHP, se si vuole ottenere il pid di un processo, potrebbe essere necessario prefissare i comandi con exec. Si legga la issue # di Symfony per capire perché questo accada. Disabling Output New in version.: I metodi disableoutput() e enableoutput() sono stati introdotti in Symfony.. Poiché l'output standard e l'output di errore sono sempre recuperati dal processo sottostante, in alcuni casi potrebbe essere conveniente disabilitare l'output, per risparmiare memoria. Usare disableoutput() 0 e enableoutput() per abilitare questa caratteristica: Listing - use Symfony\Component\Process\Process; $process = new Process('/usr/bin/php worker.php'); $process->disableoutput(); $process->run(); generated on August, 0 Chapter : Il componente Process 0
291 Non si può abilitare o disabilitare l'output mentre il processo sta girando. Se si disabilita l'output, non si può accedere a getoutput, getincrementaloutput, geterroroutput o getincrementalerroroutput. Inoltre, non si può passare un callback ai metodi start, run o mustrun né usare setidletimeout. generated on August, 0 Chapter : Il componente Process
292 Chapter Il componente PropertyAccess Il componente PropertyAccess fornisce funzioni per leggere e scrivere da/a oggetti o array, usando una semplice notazione stringa. Installazione Si può installare il componente in due modi: Installarlo tramite Composer (symfony/property-access su Packagist ); Usare il repository Git ufficiale ( ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Uso Il punto di ingresso di questo componente è il factory PropertyAccess::createPropertyAccessor. Questo factory creerà una nuova istanza della classe PropertyAccessor con la configurazione predefinita: Listing - use Symfony\Component\PropertyAccess\PropertyAccess; $accessor = PropertyAccess::createPropertyAccessor(); generated on August, 0 Chapter : Il componente PropertyAccess
293 New in version.: Il metodo createpropertyaccessor() precedenza si chiamava getpropertyaccessor(). è stato introdotto in Symfony.. In Lettura da array Si può leggere un array con il metodo PropertyAccessor::getValue. Lo si può fare usando la notazione indice, usata in PHP: Listing - $person = array( 'first_name' => 'Wouter', ); echo $accessor->getvalue($person, '[first_name]'); // 'Wouter' echo $accessor->getvalue($person, '[age]'); // null Come si può vedere, il metodo restituisce null quando l'indice non esiste. Si possono usare anche array a più dimensioni: Listing - 0 $persons = array( array( 'first_name' => 'Wouter', ), array( 'first_name' => 'Ryan', ) ); echo $accessor->getvalue($persons, '[0][first_name]'); // 'Wouter' echo $accessor->getvalue($persons, '[][first_name]'); // 'Ryan' Lettura da oggetti Il metodo getvalue è molto robusto, lo si può vedere quando si ha a che fare con oggetti. Accesso a proprietà pubbliche Per leggere da proprietà, usare la notazione con il punto: Listing - $person = new Person(); $person->firstname = 'Wouter'; echo $accessor->getvalue($person, 'firstname'); // 'Wouter' $child = new Person(); $child->firstname = 'Pluto'; $person->children = array($child);. generated on August, 0 Chapter : Il componente PropertyAccess
294 0 echo $accessor->getvalue($person, 'children[0].firstname'); // 'Pluto' L'accesso a proprietà pubbliche è l'ultima opzione usata da PropertyAccessor. Prima prova ad accedere al valore usando i metodi, prima di usare direttamente la proprietà. Per esempio, se si ha una proprietà pubblica con un metodo getter, sarà usato il getter. Uso dei getter Il metodo getvalue supporta anche la lettura tramite getter. Il metodo sarà creato usando le comuni convenzioni di nomenclatura dei getter. Mette in maiuscolo il nome (first_name diventa FirstName) e aggiunge il prefisso get. Il metodo diventa quindi getfirstname: Listing - 0 class Person private $firstname = 'Wouter'; public function getfirstname() return $this->firstname; $person = new Person(); echo $accessor->getvalue($person, 'first_name'); // 'Wouter' Uso di hasser/isser Se non viene trovato un getter, l'accessor cercherà un isser o un hasser. Tale metodo è creato nello stesso modo dei getter, quindi si può fare qualcosa come: Listing class Person private $author = true; private $children = array(); public function isauthor() return $this->author; public function haschildren() return 0!== count($this->children); $person = new Person(); if ($accessor->getvalue($person, 'author')) generated on August, 0 Chapter : Il componente PropertyAccess
295 echo 'È un autore'; if ($accessor->getvalue($person, 'children')) echo 'Ha dei figli'; Produrrà: È un autore Metodo magico get() Infine, getvalue può usare anche il metodo magico get: Listing - 0 class Person private $children = array( 'Wouter' => array(...), ); public function get($id) return $this->children[$id]; $person = new Person(); echo $accessor->getvalue($person, 'Wouter'); // array(...) Metodo magico call() Alla fine, getvalue può usare il metodo magico call, ma occorre abilitare questa caratteristica, usando PropertyAccessorBuilder : Listing class Person private $children = array( 'wouter' => array(...), ); public function call($name, $args) $property = lcfirst(substr($name, )); if ('get' === substr($name, 0, )) return isset($this->children[$property])? $this->children[$property] : null; elseif ('set' === substr($name, 0, )) $value = == count($args)? $args[0] : null; $this->children[$property] = $value;. generated on August, 0 Chapter : Il componente PropertyAccess
296 $person = new Person(); // Abilita call $accessor = PropertyAccess::createPropertyAccessorBuilder() ->enablemagiccall() ->getpropertyaccessor(); echo $accessor->getvalue($person, 'wouter'); // array(...) New in version.: L'uso del metodo magico call() è stato aggiunto in Symfony.. Per impostazione predefinita, call è disabilitato, lo si può abilitare richiamando PropertyAccessorBuilder::enableMagicCallEnabled, vedere Abilitare altre caratteristiche. Scrittura su array La classe PropertyAccessor può far più che leggere semplicemente un array, può anche scrivere in un array. Lo si può fare usando il metodo PropertyAccessor::setValue : Listing - $person = array(); $accessor->setvalue($person, '[first_name]', 'Wouter'); echo $accessor->getvalue($person, '[first_name]'); // 'Wouter' // oppure // echo $person['first_name']; // 'Wouter' Scrittura su oggetti Il metodo setvalue ha le stesse caratteristiche del metodo getvalue. Si possono usare i setter, il metodo magico set o le proprietà, per impostare i valori: Listing -0 0 class Person public $firstname; private $lastname; private $children = array(); public function setlastname($name) $this->lastname = $name; public function set($property, $value). generated on August, 0 Chapter : Il componente PropertyAccess
297 0 $this->$property = $value; $person = new Person(); $accessor->setvalue($person, 'firstname', 'Wouter'); $accessor->setvalue($person, 'lastname', 'de Jong'); $accessor->setvalue($person, 'children', array(new Person())); echo $person->firstname; // 'Wouter' echo $person->getlastname(); // 'de Jong' echo $person->children; // array(person()); Si può anche usare call per impostare valori, ma occorre abilitarlo, vedere Abilitare altre caratteristiche. Listing class Person private $children = array(); public function call($name, $args) $property = lcfirst(substr($name, )); if ('get' === substr($name, 0, )) return isset($this->children[$property])? $this->children[$property] : null; elseif ('set' === substr($name, 0, )) $value = == count($args)? $args[0] : null; $this->children[$property] = $value; $person = new Person(); // Abilita call $accessor = PropertyAccess::createPropertyAccessorBuilder() ->enablemagiccall() ->getpropertyaccessor(); $accessor->setvalue($person, 'wouter', array(...)); echo $person->getwouter(); // array(...) generated on August, 0 Chapter : Il componente PropertyAccess
298 Verificare i percorsi della proprietà New in version.: I metodi PropertyAccessor::isReadable 0 e PropertyAccessor::isWritable sono stati introdotti in Symfony.. Se si vuole verificare se il metodo PropertyAccessor::getValue possa essere richamato senza doverlo effettivamente richiamare, si può usare PropertyAccessor::isReadable : Listing - $person = new Person(); if ($accessor->isreadable($person, 'firstname') Lo stesso è possibile per PropertyAccessor::setValue : Richiamare il metodo PropertyAccessor::isWritable per sapere se un percorso di proprietà possa essere aggiornato: Listing - $person = new Person(); if ($accessor->iswritable($person, 'firstname') Mischiare oggetti e array Si possono anche mischiare oggetti e array: Listing class Person public $firstname; private $children = array(); public function setchildren($children) $this->children = $children; public function getchildren() return $this->children; $person = new Person(); $accessor->setvalue($person, 'children[0]', new Person); // equivale a $person->getchildren()[0] = new Person() generated on August, 0 Chapter : Il componente PropertyAccess
299 $accessor->setvalue($person, 'children[0].firstname', 'Wouter'); // equivale a $person->getchildren()[0]->firstname = 'Wouter' echo 'Hello '.$accessor->getvalue($person, 'children[0].firstname'); // 'Wouter' // equivale a $person->getchildren()[0]->firstname Abilitare altre caratteristiche Si può configurare PropertyAccessor per abilitare caratteristiche extra. Per poterlo fare, si può usare PropertyAccessorBuilder : Listing - 0 $accessorbuilder = PropertyAccess::createPropertyAccessorBuilder(); // Abilita call $accessorbuilder->enablemagiccall(); // Disabilita call $accessorbuilder->disablemagiccall(); // Verifica se la gestione di call è abilitata $accessorbuilder->ismagiccallenabled() // true o false // Alla fine ottiene l'accessor alla proprietà configurato $accessor = $accessorbuilder->getpropertyaccessor(); // Oppure tutto insieme $accessor = PropertyAccess::createPropertyAccessorBuilder() ->enablemagiccall() ->getpropertyaccessor(); Oppure si possono passare parametri direttamente al costruttore (non raccomandato): Listing - $accessor = new PropertyAccessor(true) // abilita la gestione di call. generated on August, 0 Chapter : Il componente PropertyAccess
300 Chapter Il componente Routing Il componente Routing confronta una richiesta HTTP con un insieme di variabili di configurazione. Installazione È possibile installare il componente in diversi modi: Utilizzando il repository ufficiale su Git ( ); Installandolo via Composer (symfony/routing su Packagist ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Utilizzo Per poter usare un sistema di rotte di base, sono necessari tre elementi: Una RouteCollection, che contiene la definizione delle rotte (un'istanza della classe Route ) Un RequestContext, che contiene informazioni relative alla richiesta Un UrlMatcher, che associa la richiesta a una singola rotta Il seguente esempio assume che l'autoloader sia già stato configurato in modo tale da caricare il componente Routing: generated on August, 0 Chapter : Il componente Routing 00
301 Listing - 0 use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $rotta = new Route('/pippo', array('controller' => 'MioControllore')) $rotte = new RouteCollection(); $rotte->add('nome_rotta', $rotta); $contesto = new RequestContext($_SERVER['REQUEST_URI']); $abbinatore = new UrlMatcher($rotte, $contesto); $parametri = $abbinatore->match( '/pippo'); // array('controller' => 'MioControllore', '_route' => 'nome_rotta') Particolare attenzione va data al'utilizzo di $_SERVER['REQUEST_URI'], perché potrebbe contenere qualsiasi parametro della richiesta inserito nel'url creando problemi con l'abbinamento alla rotta. Un semplice modo per risolvere il problema è usare il componente HTTPFoundation come spiegato sotto. È possibile aggiungere un numero qualsiasi di rotte a una classe RouteCollection. Il metodo RouteCollection::add() accetta due parametri. Il primo è il nome della rotta, il secondo è un oggetto Route, il cui costruttore si aspetta di ricevere un percorso URL e un array di variabili personalizzate. L'array di variabili personalizzate può essere qualsiasi cosa che abbia senso per l'applicazione e viene restituito quando la rotta viene abbinata. Se non viene trovato alcun abbinamento con la rotta verrà lanciata una ResourceNotFoundException 0. Oltre al'array di variabili personalizzate, viene aggiunta la chiave _route che conterrà il nome della rotta abbinata. Definire le rotte La definizione completa di una rotta può contenere fino a quattro parti: #. Lo schema dell'url della rotta. È questo il valore con il quale si confronta l'url passato a RequestContext. Può contenere diversi segnaposto (per esempio segnaposto) che possono abbinarsi a parti dinamiche dell'url. #. Un array di valori base. Contiene un array di valori arbitrari che verranno restituiti quando la richiesta viene abbinata alla rotta. #. Un array di requisiti. Definisce i requisiti per i valori dei segnaposto in forma di espressione regolare. #. Un array di opzioni. Questo array contiene configurazioni interne per le rotte e, solitamente, sono la parte di cui meno ci si interessa.. Un host. Viene cercata corrispondenza con l'host della richiesta. Vedere Corrispondere una rotta in base all'host per ulteriori dettagli.. Un array di schemi. Restringe a specifici schemi HTTP (http, https).. Un array di metodi. Restringe a specifici metodi di richiesta HTTP (HEAD, GET, POST,...). Si prenda la seguente rotta, che combina diversi dei concetti esposti: generated on August, 0 Chapter : Il componente Routing 0
302 Listing $route = new Route( '/archivio/mese', // pattern per la rotta array('controller' => 'mostraarchivio'), // valori predefiniti array('mese' => '[0-]-[0-]'), // requisiti array(), // opzioni 'sottodominio.example.com', // host array(), // schemi array() // metodi ); $parametri = $abbinatore->match('/archivio/0-0'); // array( // 'controller' => 'mostraarchivio', // 'mese' => 0-0', // 'subdomain' => 'www', // '_route' =>... // ) $parametri = $abbinatore->match('/archivio/pippo'); // lancia una ResourceNotFoundException In questo caso la rotta viene trovata con /archivio/0/0, perché il segnaposto mese è associabile alla espressione regolare definita. Invece, per /archivio/pippo, non verrà trovata nessuna corrispondenza perché "pippo" non rispetta i requisiti del segnaposto. Per creare una corrispondenza che trovi tutti gli URL che inizino con un determinato percorso e terminino con un suffisso arbitrario, è possibile usare la seguente definizione: Listing - $rotta = new Route( '/inizio/suffisso', array('suffisso' => ''), array('suffisso' => '.*') ); Usare i prefissi È possibile aggiungere sia rotte che nuove istanze di RouteCollection ad un'altra collezione. In questo modo si possono creare alberi di rotte. Inoltre è possibile definire dei prefissi, requisiti predefiniti e opzioni predefinite per tutte le rotte di un sotto-albero, con i metodi forniti dalla classe RouteCollection: Listing - $collezioneradice = new RouteCollection(); $subcollezione = new RouteCollection(); $subcollezione->add(...); $subcollezione->add(...); $subcollezione->addprefix('/prefisso'); $subcollezione->adddefaults(array(...)); $subcollezione->addrequirements(array(...)); $subcollezione->addoptions(array(...));. generated on August, 0 Chapter : Il componente Routing 0
303 0 $subcollezione->sethost('admin.example.com'); $subcollezione->setmethods(array('post')); $subcollezione->setschemes(array('https')); $collezioneradice->addcollection($subcollezione); Configurare i parametri della richiesta RequestContext fornisce informazioni relative alla richiesta attuale. Con questa classe, tramite il suo costruttore, è possibile definire tutti i parametri di una richiesta HTTP: Listing - 0 public function construct( $baseurl = '', $method = 'GET', $host = 'localhost', $scheme = 'http', $httpport = 0, $httpsport =, $path = '/', $querystring = '' ) È possibile passare i valori della variabile $_SERVER per popolare RequestContext. Ma se si utilizza il componente HttpFoundation, è possibile usarne la classe Request per riempire la RequestContext con una scorciatoia: Listing - use Symfony\Component\HttpFoundation\Request; $context = new RequestContext(); $context->fromrequest(request::createfromglobals()); Generare un URL Mentre la classe UrlMatcher cerca di trovare una rotta che sia adeguata a una determinata richiesta, è anche possibile creare degli URL a partire da una determinata rotta: Listing - 0 use Symfony\Component\Routing\Generator\UrlGenerator; $rotte = new RouteCollection(); $rotte->add('mostra_articolo', new Route('/mostra/slug')); $contesto = new RequestContext($_SERVER['REQUEST_URI']); $generatore = new UrlGenerator($rotte, $contesto); $url = $generatore->generate('mostra_articolo', array( 'slug' => 'articolo-sul-mio-blog' generated on August, 0 Chapter : Il componente Routing 0
304 )); // /mostra/articolo-sul-mio-blog Se fosse stato definito il requisito dello _scheme, verrebbe generata un URL assoluto nel caso in cui lo schema corrente RequestContext non fosse coerente con i requisiti. Caricare le rotte da un file Si è visto come sia semplice aggiungere rotte a una collezione direttamente tramite PHP. Ma è anche possibile caricare le rotte da diversi tipi di file differenti. Il componente del Routing contiene diverse classi di caricamento, ognuna delle quali fornisce l'abilità di caricare collezioni di definizioni di rotte da file esterni di diverso formato. Ogni caricatore si aspetta di ricevere un'istanza di FileLocator come argomento del costruttore. Si può usare il FileLocator per definire una array di percorsi nei quali il caricatore andrà a cercare i file richiesti. Se il file viene trova, il caricatore restituisce una RouteCollection 0. Si utilizza il caricatore YamlFileLoader, allora la definizione delle rotte sarà simile alla seguente: Listing - # routes.yml rotta: pattern: /pippo defaults: _controller: 'MioControllore::pippoAction' rotta: pattern: /pippo/pluto defaults: _controller: 'MioControllore::pippoPlutoAction' Per caricare questo file, è possibile usare il seguente codice. Si presume che il file routes.yml sia nella stessa cartella in cui si trova i codice: Listing - use Symfony\Component\Config\FileLocator; use Symfony\Component\Routing\Loader\YamlFileLoader; // controlla al'interno della cartella *corrente* $cercatore = new FileLocator(array( DIR )); $caricatore = new YamlFileLoader($cercatore); $collezione = $caricatore->load('routes.yml'); Oltre a YamlFileLoader ci sono altri due caricatori che funzionano nello stesso modo: XmlFileLoader PhpFileLoader Se si usa PhpFileLoader sarà necessario fornire il nome del file php che restituirà una RouteCollection : generated on August, 0 Chapter : Il componente Routing 0
305 Listing -0 0 // FornitoreDiRotte.php use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collezione = new RouteCollection(); $collezione->add( 'nome_rotta', new Route('/pippo', array('controller' => 'ControlloreEsempio')) ); return $collezione; Rotte e Closure Esiste anche un ClosureLoader, il quale chiama una closure e ne utilizza il risultato come una RouteCollection : Listing - use Symfony\Component\Routing\Loader\ClosureLoader; $closure = function() return new RouteCollection(); ; $caricatore = new ClosureLoader(); $collezione = $caricatore->load($closure); Rotte e annotazioni Ultime, ma non meno importanti sono AnnotationDirectoryLoader e AnnotationFileLoader usate per caricare le rotte a partire dalle annotazioni delle classi. Questo articolo non tratterà i dettagli di queste classi. Il router tutto-in-uno La classe Router 0 è un pacchetto tutto-in-uno che permette i usare rapidamente il componente Routing. Il costruttore si aspetta di ricevere l'istanza di un caricatore, un percorso per la definizione della rotta principale e alcuni altri parametri: Listing - public function construct( LoaderInterface $loader, $resource, array $options = array(), RequestContext $context = null, array $defaults = array() ); generated on August, 0 Chapter : Il componente Routing 0
306 Tramite l'opzione cache_dir è possibile abilitare la cache delle rotte (cioè se si fornisce un percorso) o disabilitarla (se viene configurata a null). La cache è realizzata automaticamente nello sfondo, qualora la si volesse utilizzare. Un semplice esempio di come sia fatta la classe Router è riportato di seguito: Listing - 0 $cercatore = new FileLocator(array( DIR )); $contestorichiesta = new RequestContext($_SERVER['REQUEST_URI']); $router = new Router( new YamlFileLoader($cercatore), 'routes.yml', array('cache_dir' => DIR.'/cache'), $contestorichiesta, ); $router->match('/pippo/pluto'); Se si utilizza la cache, il componente Routing compilerà nuove classi che saranno salvate in cache_dir. Questo significa che lo script deve avere i permessi di scrittura nella cartella indicata.. generated on August, 0 Chapter : Il componente Routing 0
307 Chapter 0 Corrispondere una rotta in base all'host Si può anche far corrispondere l'host HTTP della richiesta in entrata. Listing 0- mobile_homepage: path: / host: m.example.com defaults: _controller: AcmeDemoBundle:Main:mobileHomepage homepage: path: / defaults: _controller: AcmeDemoBundle:Main:homepage Entrambe le rotte corrispondono allo stesso percorso /, ma la prima corrisponderà solo se l'host è m.example.com. Usare i segnaposto L'opzione host usa la stessa sintassi del sistema di corrispondenza dei percorsi. Questo vuol dire che si possono usare segnaposto nel nome dell'host: Listing 0- projects_homepage: path: / host: "nome progetto.example.com" defaults: _controller: AcmeDemoBundle:Main:mobileHomepage homepage: path: / defaults: _controller: AcmeDemoBundle:Main:homepage Si possono anche impostare requisiti e opzioni predefinite per i segnaposto. Per esempio, se si vuole che m.example.com e mobile.example.com corrispondano, si può usare: Listing 0- generated on August, 0 Chapter 0: Corrispondere una rotta in base all'host 0
308 0 mobile_homepage: path: / host: "subdomain.example.com" defaults: _controller: AcmeDemoBundle:Main:mobileHomepage subdomain: m requirements: subdomain: m mobile homepage: path: / defaults: _controller: AcmeDemoBundle:Main:homepage Uso dei parametri dei servizi Si possono anche usare i parametri dei servizi, se non si vuole scrivere il nome dell'host direttamente: Listing 0-0 mobile_homepage: path: / host: "m.domain" defaults: _controller: AcmeDemoBundle:Main:mobileHomepage domain: "%domain%" requirements: domain: "%domain%" homepage: path: / defaults: _controller: AcmeDemoBundle:Main:homepage Assicurarsi di includere anche un'opzione per il segnaposto subdomain, altrimenti occorrerà includere i valori dei sottodomini ogni volta che si genera la rotta. Corrispondenza dell'host su rotte importate Si può impostare l'opzione host sulle rotte importate: Listing 0- acme_hello: resource: "@AcmeHelloBundle/Resources/config/routing.yml" host: "hello.example.com" L'host hello.example.com sarà impostato su ciascuna rotta caricata dalla nuova risorsa delle rotte. Testare i controllori Se si vuole far funzionare la corrispondenza degli URL nei test funzionali, occorre impostare l'header HTTP_HOST negli oggetti richiesta. generated on August, 0 Chapter 0: Corrispondere una rotta in base all'host 0
309 Listing 0- $crawler = $client->request( 'GET', '/homepage', array(), array(), array('http_host' => 'm.'. $client->getcontainer()->getparameter('domain')) ); generated on August, 0 Chapter 0: Corrispondere una rotta in base all'host 0
310 Chapter Il componente Security Il componente Security fornisce un sistema completo di sicurezza per un'applicazione web. Dispone di strutture per l'autenticazione con HTTP basic authentication o con digest authentication, form di login interattivo o login con certificato X.0, ma consente anche di implementare strategie di autenticazione personalizzate. Inoltre, il componente fornisce diversi modi per autorizzare gli utenti autenticati, in base ai loro ruoli, e contiene un sistema di ACL avanzato. Installazione Il componente può essere installato in due modi: Installandolo tramite Composer (symfony/security su Packagist ); Utilizzando il repository Git ufficiale ( ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Sezioni Il Firewall e il contesto di sicurezza Autenticazione Autorizzazione. generated on August, 0 Chapter : Il componente Security 0
311 Chapter Il Firewall e il contesto di sicurezza Il contesto di sicurezza è un concetto centrale nel componente Security: è un'istanza di SecurityContextInterface. Quando tutti i passi nel processo di autenticazione dell'utente sono stati eseguiti con successo, si può chiedere al contesto di sicurezza se l'utente autenticato ha accesso a una determinata azione o risorsa dell'applicazione: Listing - 0 use Symfony\Component\Security\Core\SecurityContext; use Symfony\Component\Security\Core\Exception\AccessDeniedException; // istanza di Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface $authenticationmanager =...; // istanza di Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface $accessdecisionmanager =...; $securitycontext = new SecurityContext( $authenticationmanager, $accessdecisionmanager ); autenticare l'utente if (!$securitycontext->isgranted('role_admin')) throw new AccessDeniedException(); Leggere le sezioni dedicate per approfondire Autenticazione e Autorizzazione.. generated on August, 0 Chapter : Il Firewall e il contesto di sicurezza
312 Un firewall per le richieste HTTP L'autenticazione dell'utente è eseguita dal firewall. Un'applicazione può avere più aree protette, quindi il firewall viene configurato usando una mappa di tali aree. Per ciascuna area, la mappa contiene l'indicazione di una richiesta e un insieme di ascoltatori. L'indicazione della richiesta dà al firewall la possibilità di associare la richiesta corrente a un data area protetta. Quindi viene chiesto agli ascoltatori se la richiesta corrente possa essere usata per autenticare l'utente: Listing - 0 use Symfony\Component\Security\Http\FirewallMap; use Symfony\Component\HttpFoundation\RequestMatcher; use Symfony\Component\Security\Http\Firewall\ExceptionListener; $map = new FirewallMap(); $requestmatcher = new RequestMatcher('^/secured-area/'); // istanze di Symfony\Component\Security\Http\Firewall\ListenerInterface $listeners = array(...); $exceptionlistener = new ExceptionListener(...); $map->add($requestmatcher, $listeners, $exceptionlistener); La mappa di firewall sarà fornita al firewall come primo parametro, insieme con il distributore di eventi, che è usato da HttpKernel : Listing - 0 use Symfony\Component\Security\Http\Firewall; use Symfony\Component\HttpKernel\KernelEvents; // l'eventdispatcher usato da HttpKernel $dispatcher =...; $firewall = new Firewall($map, $dispatcher); $dispatcher->addlistener( KernelEvents::REQUEST, array($firewall, 'onkernelrequest') ); Il firewall viene registrato per ascoltare l'evento kernel.request, che sarà distribuito da HttpKernel all'inizio di ogni richiesta che esso processa. In questo modo, il firewall può impedire all'utente di proseguire oltre quanto consentito. Ascoltatori dei firewall Quando al firewall viene notificato l'evento kernel.request, esso richiede la mappa dei firewall, se la richiesta corrisponde a una delle aree protette. La prima area protetta corrispondente alla richiesta restituirà un insieme di ascoltatori di firewall corrispondenti (ciascuno dei quali implementa ListenerInterface ). A questi ascoltatori sarà chiesto di gestire la richiesta corrente. Questo, di base, vuol dire: trovare se la richiesta corrente contiene informazioni su come l'utente possa essere autenticato (per esempio il listener basic HTTP authentication verifica se la richiesta ha un header chiamato PHP_AUTH_USER) generated on August, 0 Chapter : Il Firewall e il contesto di sicurezza
313 Ascoltatore delle eccezioni Se uno degli ascoltatori lancia una AuthenticationException, l'ascoltatore delle eccezioni fornito durante l'aggiunta di aree protette alla mappa dei firewall interverrà. L'ascoltatore di eccezioni determina cosa accadrà successivamente, in base ai parametri che riceve durante la sua creazione. Può iniziare la procedura di autenticazione, magari chiedere all'utente di fornire nuovamente le sue credenziali (quando si è autenticato tramite un cookie "ricordami") oppure trasformare l'eccezione in AccessDeniedHttpException, che successivamente risulterà in una risposta "HTTP/. 0: Access Denied". Punti di ingresso Quando l'utente non è autenticato (p.e. quando il contesto di sicurezza non ha ancora alcun token), il punto di ingresso del firewall sarà richiamato, per "iniziare" il processo di autenticazione. Un punto di ingresso dovrebbe implementare AuthenticationEntryPointInterface, che un unico metodo: start(). Questo metodo riceve l'oggetto Request corrente e l'ascoltatore di eccezioni che è stato attivato. Il metodo dovrebbe restituire un oggetto Response. Potrebbe essere, per esempio, la pagina che contiene il form di login oppure, nel caso di basic HTTP authentication, una risposta con un header WWW- Authenticate, che chiederà all'utente di fornire nome e password. Flusso: firewall, autenticazione, autorizzazione Forse ora si può capire meglio come funziona il flusso del contesto di sicurezza:. il firewall viene registrato come acoltatore sulla richiesta;. all'inizio della richiesta, il firewall controlla la mappa dei firewall per vedere se ce n'è uno attivo sull'url;. se nella mappa viene trovato un firewall corrispondente all'url, i suoi ascoltatori vengono notificati. ciascun ascoltatore verifica se la richiesta corrente contiene informazioni di autenticazione. Un ascoltatore può (a) autenticare un utente, (b) lanciare una AuthenticationException, o (c) non far nulla (perché non ci sono informazioni di autenticazione nella richiesta);. una volta che l'utente è autenticato, si userà Autorizzazione per negare l'accesso a determinate risorse. Leggere le prossime sezioni per saperne di più su Autenticazione e Autorizzazione generated on August, 0 Chapter : Il Firewall e il contesto di sicurezza
314 Chapter Autenticazione Quando una richiesta punta a un'area protetta e se uno degli ascoltatori della mappa dei firewall è in grado di estrarre le credenziali dell'utente dall'oggetto Request corrente, dovrebbe creare un token, che contiene tali credenziali. La cosa successiva che l'ascoltatore dovrebbe fare è chiedere al gestore di autenticazione di validare il token fornito e restituire un token autenticato, se le credenziali fornite sono state riconosciute come valide. L'ascoltatore quindi dovrebbe memorizzare il token autenticato nel contesto di sicurezza: Listing use Symfony\Component\Security\Http\Firewall\ListenerInterface; use Symfony\Component\Security\Core\SecurityContextInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; class SomeAuthenticationListener implements ListenerInterface /** SecurityContextInterface */ private $securitycontext; /** AuthenticationManagerInterface */ private $authenticationmanager; /** string Identifica univocamente l'area protetta */ private $providerkey; public function handle(getresponseevent $event). generated on August, 0 Chapter : Autenticazione
315 0 0 $request = $event->getrequest(); $username =...; $password =...; $unauthenticatedtoken = new UsernamePasswordToken( $username, $password, $this->providerkey ); $authenticatedtoken = $this ->authenticationmanager ->authenticate($unauthenticatedtoken); $this->securitycontext->settoken($authenticatedtoken); Un token può essere di qualsiasi classe, a patto che implementi TokenInterface. Il gestore di autenticazione Il gestore di autenticazione predefinito è un'istanza di AuthenticationProviderManager : Listing - 0 use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager; // instanze di Symfony\Component\Security\Core\Authentication\AuthenticationProviderInterface $providers = array(...); $authenticationmanager = new AuthenticationProviderManager($providers); try $authenticatedtoken = $authenticationmanager ->authenticate($unauthenticatedtoken); catch (AuthenticationException $failed) // autenticazione fallita AuthenticationProviderManager, quando istanziata, riceve vari fornitori di autenticazione, ciascuno che supporta un diverso tipo di token. Ovviamente, si può scrivere un proprio gestore di autenticazione, basta che implementi AuthenticationManagerInterface generated on August, 0 Chapter : Autenticazione
316 Fornitori di autenticazione Ogni fornitore (poiché implementa AuthenticationProviderInterface ) ha un metodo supports() da cui AuthenticationProviderManager può determinare se supporti il dato token. Se questo è il caso, il gestore richiama il metodo AuthenticationProviderInterface::authenticate del fornitore. Tale metodo dovrebbe restituire un token autenticato o lanciare una AuthenticationException (o un'eccezione che la estenda). Autenticare utenti con nome e password Un fornitore di autenticazione proverà ad autenticare un utente in base alle credenziali fornite. Solitamente queste sono un nome utente e una password. La maggior parte delle applicazioni web memorizzano i nomi utente e un hash delle password combinate con un sale generato casualmente. Ciò vuol dire che l'autenticazione media consiste nel recuperare il sale e l'hash della password dal sistema di memorizzazione dei dati degli utenti, trasformare in hash la password appena fornita dall'utente (p.e. in un form di login) con il sale e confrontare entrambi, per determinare se la password fornita sia valida. Tale funzionalità è offerta da DaoAuthenticationProvider. Questa classe recupera i dati dell'utente da un UserProviderInterface`0, usa un PasswordEncoderInterface per creare un hash della password e restituisce un token autenticato, se la password è valida: Listing use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider; use Symfony\Component\Security\Core\User\UserChecker; use Symfony\Component\Security\Core\User\InMemoryUserProvider; use Symfony\Component\Security\Core\Encoder\EncoderFactory; $userprovider = new InMemoryUserProvider( array( 'admin' => array( // la password è "foo" 'password' => 'FZZQIkAUTZBYkoC+GsReLfmSKDsfodsLYQt+aEWoaircfMpmaLbPBhFOBiiFyLfuZmTSUwzZg==', 'roles' => array('role_admin'), ), ) ); // per alcuni controlli ulteriori: account abilitato, bloccato, scaduto, ecc.? $userchecker = new UserChecker(); // un array di codificatori di password (vedere più avanti) $encoderfactory = new EncoderFactory(...); $provider = new DaoAuthenticationProvider( $userprovider, $userchecker, 'secured_area', $encoderfactory. AuthenticationProviderInterface.html#method_supports. AuthenticationProviderInterface::authenticate.html generated on August, 0 Chapter : Autenticazione
317 ); $provider->authenticate($unauthenticatedtoken); L'esempio sopra dimostra l'uso di un fornitore "in-memory" (in memoria), ma si può usare qualsiasi fornitore di utente, purché implementi UserProviderInterface. È anche possibile far cercare i dati dell'utente a più di un fornitore di utenti, usando ChainUserProvider. Il factory codificatore di password DaoAuthenticationProvider usa un factory codificatore per creare un codificatore di password per un dato tipo di utente. Questo consente di usare diverse strategie di codifica per diversi tipi di utenti. La classe predefinita EncoderFactory riceve un array di codificatori: Listing - 0 use Symfony\Component\Security\Core\Encoder\EncoderFactory; use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; $defaultencoder = new MessageDigestPasswordEncoder('sha', true, 000); $weakencoder = new MessageDigestPasswordEncoder('md', true, ); $encoders = array( 'Symfony\\Component\\Security\\Core\\User\\User' => $defaultencoder, 'Acme\\Entity\\LegacyUser' => $weakencoder, ); $encoderfactory = new EncoderFactory($encoders); Ogni codificatore deve implementare PasswordEncoderInterface o essere un array con chiavi class e arguments, che consente al factory codificatore di costruire il codificatore solo quando necessario. Creare un codificatore di password Ci sono molti codificatori di password predefiniti. Se si ha l'esigenza di crearne uno nuovo, basta seguire le seguenti tre regole:. La classe deve implementare PasswordEncoderInterface ;. Le implementazioni di encodepassword() e ispasswordvalid() devono innanzitutto assicurarsi che la password non sia troppo lunga, vale a dire che la password non superi i 0 caratteri. Questo per motivi di sicurezza (vedere CVE ). Si può usare il metodo ispasswordtoolong() per eseguire questo controllo: Listing generated on August, 0 Chapter : Autenticazione
318 0 0 use Symfony\Component\Security\Core\Exception\BadCredentialsException; class FoobarEncoder extends BasePasswordEncoder public function encodepassword($raw, $salt) if ($this->ispasswordtoolong($raw)) throw new BadCredentialsException('Invalid password.'); public function ispasswordvalid($encoded, $raw, $salt) if ($this->ispasswordtoolong($raw)) return false; Usare codificatori di password Quando il metodo getencoder() del factory codificatore di password viene richiamato con l'oggetto utente come primo parametro, restituirà un codificatore di tipo PasswordEncoderInterface, che va usato per codificare la password dell'utente: Listing - 0 // recupera un utente di tipo Acme\Entity\LegacyUser $user =... // la password immessa, p.e. durante una registrazione $plainpassword =...; $encoder = $encoderfactory->getencoder($user); // restituirà $weakencoder (vedere sopra) $encodedpassword = $encoder->encodepassword($plainpassword, $user->getsalt()); $user->setpassword($encodedpassword); salvare l'utente Se ora si vuole verificare se la password fornita (p.e. durante un login) sia corretta, si può usare: Listing - // recupera Acme\Entity\LegacyUser $user =...; // la password immessa, p.e. da un form di login $plainpassword =...; $validpassword = $encoder->ispasswordvalid( $user->getpassword(), // la password codificata. generated on August, 0 Chapter : Autenticazione
319 0 ); $plainpassword, $user->getsalt() // la password fornita generated on August, 0 Chapter : Autenticazione
320 Chapter Autorizzazione Quando uno dei fornitori di autenticazione (vedere Fornitori di autenticazione) ha verificato il token non ancora autenticato, sarà restituito un token autenticato. L'ascoltatore di autenticazione dovrebbe impostare questo token direttamente in SecurityContextInterface usando il suo metodo settoken(). Da quel momento, l'utente è autenticato, cioè identificato. Ora, altre parti dell'applicazione possono usare il token per decidere se l'utente possa o meno richiedere un certo URI o modificare un certo oggetto. Questa decisione sarà presa da un'istanza di AccessDecisionManagerInterface. Una decisione di autorizzazione sarà sempre basata su alcuni aspetti: Il token corrente Per esempio il metodo getroles() del token può essere usato per recuperare i ruoli dell'utente attuale (p.e. ROLE_SUPER_ADMIN) o una decisione potrebbe essere basata sulla classe del token. Un insieme di attributi Ogni attributo sta per un certo diritto che l'utente dovrebbe avere, p.e. ROLE_ADMIN per assicurarsi che l'utente sia un amministratore. Un oggetto (facoltativo) Un oggetto per cui il controllo di accesso necessiti di essere verificato, come un oggetto articolo o un oggetto commento generated on August, 0 Chapter : Autorizzazione 0
321 Gestore delle decisioni di accesso Poiché decidere se un utente sia o meno autorizzato a eseguire una certa azione può essere un processo complicato, AccessDecisionManager dipende da molti votanti ed emette un verdetto finale in base a tutti i voti (siano essi positivi, negativi o neutrali) ricevuti. Riconosce diverse strategie: affirmative (predefinita) consensus unanimous garantisce l'accesso se uno dei votanti restituisce una risposta affermativa; garantisce l'accesso se ci sono più votanti affermativi che negativi; garantisce l'accesso se nessuno dei votanti è negativo; Listing use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; // instanze di Symfony\Component\Security\Core\Authorization\Voter\VoterInterface $voters = array(...); // uno tra "affirmative", "consensus", "unanimous" $strategy =...; // se garantire o meno l'accesso quando tutti i votanti si astengono $allowifallabstaindecisions =...; // se garantire o meno l'accesso quando non c'è maggioranza (si applica solo alla strategia "consensus") $allowifequalgranteddenieddecisions =...; $accessdecisionmanager = new AccessDecisionManager( $voters, $strategy, $allowifallabstaindecisions, $allowifequalgranteddenieddecisions ); Si può modificare la strategia predefinita nella configurazione. Votanti I votanti sono istanze di VoterInterface, il che vuol dire che devono implementare alcuni metodi, che consentono al gestore di decisioni di usarli: supportsattribute($attributo) usato per verificare se il votante sa come gestire il dato attributo; supportsclass($classe) usato per verificare se il votante può garantire o negare accesso per un oggetto di una data classe;. generated on August, 0 Chapter : Autorizzazione
322 vote(tokeninterface $token, $object, array $attributi) questo metodo eseguira l'effettiva votazione e restituirà un valore pari a una delle costanti di classe di VoterInterface, cioè VoterInterface::ACCESS_GRANTED, VoterInterface::ACCESS_DENIED o VoterInterface::ACCESS_ABSTAIN; Il componente Security contiene alcuni votanti standard, che coprono diversi casi d'uso: AuthenticatedVoter Il votante AuthenticatedVoter supporta gli attributi IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_REMEMBERED e IS_AUTHENTICATED_ANONYMOUSLY e garantisce accesso in base all'attuale livello di autenticazione, cioè se l'utente è autenticato pienamente o solo in base a un cookie "ricordami", o ancora se è autenticato anonimamente. Listing - 0 use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; $anonymousclass = 'Symfony\Component\Security\Core\Authentication\Token\AnonymousToken'; $remembermeclass = 'Symfony\Component\Security\Core\Authentication\Token\RememberMeToken'; $trustresolver = new AuthenticationTrustResolver($anonymousClass, $remembermeclass); $authenticatedvoter = new AuthenticatedVoter($trustResolver); // istanza di Symfony\Component\Security\Core\Authentication\Token\TokenInterface $token =...; // un qualsiasi oggetto $object =...; $vote = $authenticatedvoter->vote($token, $object, array('is_authenticated_fully'); RoleVoter Il votante RoleVoter supporta attributi che iniziano con ROLE_ e garantisce accesso all'utente quando gli attributi ROLE_* richiesti possono essere trovati nell'array dei ruoli restituiti dal metodo getroles() 0 del token: Listing - use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter; $rolevoter = new RoleVoter('ROLE_'); $rolevoter->vote($token, $object, 'ROLE_ADMIN'); RoleHierarchyVoter Il votante RoleHierarchyVoter estende RoleVoter e fornisce alcune funzionalità aggiuntive: sa come gestire una gerarchia di ruoli. Per esempio un ruolo ROLE_SUPER_ADMIN potrebbe avere dei sottoruoli ROLE_ADMIN e ROLE_USER, in modo che se un certo oggetto richiede all'utente di avere il ruolo generated on August, 0 Chapter : Autorizzazione
323 ROLE_ADMIN, sia garantito accesso agli utenti che in effetti hanno il ruolo ROLE_ADMIN, ma anche a quelli che hanno il ruolo ROLE_SUPER_ADMIN: Listing - 0 use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter; use Symfony\Component\Security\Core\Role\RoleHierarchy; $hierarchy = array( 'ROLE_SUPER_ADMIN' => array('role_admin', 'ROLE_USER'), ); $rolehierarchy = new RoleHierarchy($hierarchy); $rolehierarchyvoter = new RoleHierarchyVoter($roleHierarchy); Quando si crea il proprio votante, ovviamente si può usare il suo costruttore per iniettare una dipendenza eventualmente necessaria per prendere una decisione. Ruoli I ruoli sono oggetti che danno espressioni ad alcuni diritti posseduti dall'utente. Il solo requisito è che implementino RoleInterface, il che vuol dire che devono avere un metodo getrole() che restituisca una stringa, rappresentazione del ruolo stesso. La classe predefinita Role restituisce semplicemente il primo parametro del suo costruttore: Listing - use Symfony\Component\Security\Core\Role\Role; $role = new Role('ROLE_ADMIN'); // mostra 'ROLE_ADMIN' echo $role->getrole(); La maggior parte dei token di autenticazione estendono AbstractToken, che vuol dire che i ruoli forniti al suo costruttore saranno automaticamente convertiti da stringhe a semplici oggetti Role. Usare il gestore di decisioni L'ascoltatore degli accessi Il gestore di decisioni degli accessi può essere usato in qualsiasi punto di una richiesta per decidere se l'utente sia o meno titolato per accedere a una data risorsa. Un metodo facoltativo, ma utile, per restringere l'accesso in base a uno schema di URL è AccessListener, che è uno degli ascoltatori del generated on August, 0 Chapter : Autorizzazione
324 firewall (vedere Ascoltatori dei firewall) che è attivato per ogni richiesta corrispondente alla mappa dei firewall (vedere Un firewall per le richieste HTTP). Usa una mappa di accesso (che deve essere un'istanza di AccessMapInterface ), la quale contiene gli schemi della richiesta e il corrispettivo insieme di attributi richiesti all'utente per aver accesso all'applicazione: Listing - 0 use Symfony\Component\Security\Http\AccessMap; use Symfony\Component\HttpFoundation\RequestMatcher; use Symfony\Component\Security\Http\Firewall\AccessListener; $accessmap = new AccessMap(); $requestmatcher = new RequestMatcher('^/admin'); $accessmap->add($requestmatcher, array('role_admin')); $accesslistener = new AccessListener( $securitycontext, $accessdecisionmanager, $accessmap, $authenticationmanager ); Contesto di sicurezza Il gestore di decisioni degli accessi è disponibile anche in altre parti dell'applicazione, tramite il metodo isgranted() di SecurityContext 0. Una chiamata a questo metodo delegherà la questione al gestore di decisioni degli accessi: Listing - 0 use Symfony\Component\Security\SecurityContext; use Symfony\Component\Security\Core\Exception\AccessDeniedException; $securitycontext = new SecurityContext( $authenticationmanager, $accessdecisionmanager ); if (!$securitycontext->isgranted('role_admin')) throw new AccessDeniedException(); generated on August, 0 Chapter : Autorizzazione
325 Chapter Sicurezza nel confronto di stringhe e nella generazione di numeri casuali Il componente Security di Symfony dispone di un insieme di utilità correlate alla sicurezza. Queste utilità sono usate da Symfony, ma possono anche essere usate in autonomia, per risolvere alcuni problemi. Confronto di stringhe Il tempo che occorre per confrontare due stringhe dipende dalle loro differenze. Questo dettaglio può essere usato da un attaccante, quando le due stringhe rappresentano una password, per esempio. Questo attacco è noto come timing attack. Internamente, quando confronta due password, Symfony usa un algoritmo a tempo costante. Si può usare la stessa strategia nel proprio codice, grazie alla classe StringUtils : Listing - use Symfony\Component\Security\Core\Util\StringUtils; // una stringa nota (p.e. una password) è uguale a una fornita dall'utente? $bool = StringUtils::equals($stringaNota, $stingautente); Per evitare attacchi di tipo timing attack, la stringa nota deve essere il primo parametro e la stringa inserita dall'utente il secondo parametro generated on August, 0 Chapter : Sicurezza nel confronto di stringhe e nella generazione di numeri casuali
326 Generazione di un numero casuale sicuro Ogni volta che si deve generare un numero casuale sicuro, si raccomanda caldamente l'uso della classe SecureRandom : Listing - use Symfony\Component\Security\Core\Util\SecureRandom; $generator = new SecureRandom(); $random = $generator->nextbytes(0); Il metodo nextbytes() restituisce una stringa casuale, composta dal numero di caratteri passati come parametro (nell'esempio, 0). La classe funziona meglio quando è installato SSL. In caso in cui non sia disponibile, ai appoggia a un algoritmo interno, che ha bisogno di un seme per funzionare correttamente. In questo caso, passare un nome di file: Listing - use Symfony\Component\Security\Core\Util\SecureRandom; $generator = new SecureRandom('/percorso/in/cui/si/trova/il/seme.txt'); $random = $generator->nextbytes(0); Se si usa il framework Symfony, si può accedere direttamente all'istanza della classe dal contenitore: il nome del servizio è security.secure_random generated on August, 0 Chapter : Sicurezza nel confronto di stringhe e nella generazione di numeri casuali
327 Chapter Il componente Serializer Il componente Serializer è pensato per essere usato per trasformare oggetti in formati specifici (XML, JSON, Yaml,...) e viceversa. Per raggiungere questo scopo, il componente Serializer segue il semplice schema seguente. Come si può vedere nell'immagine, viene usato un array come tramite. In questo modo, gli Encoder si occuperanno solo di trasformare specifici formati in arrays e viceversa. Allo stesso modo, i Normalizer si occuperanno di trasformare specifici oggetti in arrays e viceversa. La serializzazione è un argomento complesso e, sebbene questo componente non possa risolvere tutti i casi, può essere uno strumento utile per lo sviluppo di strumenti che serializzano e deserializzano gli oggetti. Installazione Si può installare il componente in due modi: generated on August, 0 Chapter : Il componente Serializer
328 Installarlo via Composer (symfony/serializer su Packagist ); Usare il repository ufficiale su Git ( ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Uso L'uso del componente Serializer è molto semplice. Basta impostare la classe Serializer, specificando quali Encoder e Normalizer saranno disponibili: Listing - use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; $encoders = array(new XmlEncoder(), new JsonEncoder()); $normalizers = array(new GetSetMethodNormalizer()); $serializer = new Serializer($normalizers, $encoders); Serializzare un oggetto Per questo esempio, ipotizziamo che un progetto disponga della classe seguente: Listing namespace Acme; class Person private $age; private $name; private $sportsman; // Getter public function getname() return $this->name; public function getage() return $this->age; // Isser public function issportsman() return $this->sportsman; // Setter generated on August, 0 Chapter : Il componente Serializer
329 0 0 public function setname($name) $this->name = $name; public function setage($age) $this->age = $age; public function setsportsman($sportsman) $this->sportsman = $sportsman; Se ora vogliamo serializzare questo oggetto in JSON, ci basta usare il servizio Serializer creato in precedenza: Listing - 0 $person = new Acme\Person(); $person->setname('pippo'); $person->setage(); $person->setsportsman(false); $jsoncontent = $serializer->serialize($person, 'json'); // $jsoncontent contiene "name":"pippo","age":,"sportsman":false echo $jsoncontent; // o restituirlo in una risposta Il primo parametro di serialize() è l'oggetto da serializzare e il secondo è usato per scegliere l'encoder giusto, in questo caso JsonEncoder. Ignorare attributi durante la serializzazione New in version.: Il metodo GetSetMethodNormalizer::setIgnoredAttributes è stato introdotto in Symfony.. C'è un modo opzionale per ignorare attributi dall'oggetto originario, durante la serializzazione. Per rimuovere attributi, usare il metodo setignoredattributes() nella definizione del normalizzatore: Listing - 0 use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; $normalizer = new GetSetMethodNormalizer(); $normalizer->setignoredattributes(array('age')); $encoder = new JsonEncoder(); $serializer = new Serializer(array($normalizer), array($encoder)); $serializer->serialize($person, 'json'); // Output: "name":"foo" generated on August, 0 Chapter : Il componente Serializer
330 Deserializzare un oggetto Vediamo ora l'operazione inversa. Questa volta, l'informazione della classe People sarà codificata in formato in XML: Listing - $data = <<<EOF <person> <name>pippo</name> <age></age> <sportsman>false</sportsman> </person> EOF; $person = $serializer->deserialize($data,'acme\person','xml'); In questo caso, deserialize() ha bisogno di tre parametri:. l'informazione da decodificare. il nome della classe in cui questa informazione sarà decodificata. l'encoder usato per convertire questa informazione in un array Usare nomi in CamelCase per attributi con trattini bassi New in version.: Il metodo GetSetMethodNormalizer::setCamelizedAttributes è stato aggiunto in Symfony.. A volte i nomi di proprietà del contenuto serializzato hanno trattini bassi (p.e. first_name). Di solito, questi attributi usano metodi get o set come getfirst_name, mentre quello che si vuole è getfirstname. Per cambiare questo comportamento, usare il metodo setcamelizedattributes() 0 nella definizione del normalizzatore: Listing - 0 $encoder = new JsonEncoder(); $normalizer = new GetSetMethodNormalizer(); $normalizer->setcamelizedattributes(array('first_name')); $serializer = new Serializer(array($normalizer), array($encoder)); $json = <<<EOT "name": EOT; "pippo", "age": "", "first_name": "pluto" $person = $serializer->deserialize($json, 'Acme\Person', 'json'); Come risultato, il deserializzatore usa l'attributo first_name come se fosse stato firstname e quindi usa i metodi getfirstname e setfirstname generated on August, 0 Chapter : Il componente Serializer 0
331 Serializzare attributi booleani New in version.: Il supporto per i metodi is* in GetSetMethodNormalizer è stato introdotto in Symfony.. Se si usano i metodi isser (metodi con prefisso is, come Acme\Person::isSportsman()), il componente Serializer individuerà automaticamente e lo userà per serializzare gli attributi correlati. Uso di callback per serializzare proprietà con istanze di oggetti Quando si serializza, si può impostare un callback, per formattare una specifica proprietà di un oggetto: Listing use Acme\Person; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; use Symfony\Component\Serializer\Serializer; $encoder = new JsonEncoder(); $normalizer = new GetSetMethodNormalizer(); $callback = function ($datetime) return $datetime instanceof \DateTime? $datetime->format(\datetime::iso0) : ''; ; $normalizer->setcallbacks(array('createdat' => $callback)); $serializer = new Serializer(array($normalizer), array($encoder)); $person = new Person(); $person->setname('cordoval'); $person->setage(); $person->setcreatedat(new \DateTime('now')); $serializer->serialize($person, 'json'); // Output: "name":"cordoval", "age":, "createdat": "0-0-T0::-000" JMSSerializer Una popolare libreria, JMS serializer, fornisce una soluzione più sofisticata, sebbene più complessa. La libreria include la possibilità di configurare il modo in cui gli oggetto debbano essere serializzati/ deserializzati tramite annotazioni (oltre che YML, XML e PHP), integrazione con l'orm di Doctrine e gestione di altri casi complessi (p.e. riferimenti circolari) generated on August, 0 Chapter : Il componente Serializer
332 Chapter Il componente Stopwatch Il componente Stopwatch fornisce uno strumento per profilare il codice. Installazione Si può installare il componente in due modi: Installarlo tramite Composer (symfony/stopwatch su Packagist ); Usare il repository Git ufficiale ( ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Uso Il componente Stopwatch fornisce un modo facile e coerente di misurare il tempo di esecuzione di alcune parti di codice, in modo da non dover continuamente analizzare i microtime da soli. Basta usare la semplice classe Stopwatch : Listing - use Symfony\Component\Stopwatch\Stopwatch; $stopwatch = new Stopwatch(); // Inizia l'evento chiamato 'nomeevento' $stopwatch->start('nomeevento'); un po' di codice $event = $stopwatch->stop('nomeevento'); New in version.: Il metodo getevent() è stato introdotto in Symfony generated on August, 0 Chapter : Il componente Stopwatch
333 L'oggetto StopwatchEvent può essere recuperato dai metodi start(), stop(), lap() e getevent(). L'ultimo metodo andrebbe usato quando occorre recuperare la durata di un evento, mentre è ancora in esecuzione. Si può anche fornire un nome di categoria per un evento: Listing - $stopwatch->start('nomeevento', 'nomecategoria'); Si può pensare alle categorie come tag per gli eventi. Per esempio, il profilatore di Symfony usa le categorie per colorare diversamente il codice di vari eventi. Periodi Come sappiamo dal mondo reale, tutti i cronometri hanno due bottoni: uno per far partire e fermare il cronometro, l'altro per per le frazioni di tempo. Questo è esattamente ciò che il metodo lap() fa: Listing - $stopwatch = new Stopwatch(); // Inizia un evento di nome 'pippo' $stopwatch->start('pippo'); un po' di codice $stopwatch->lap('pippo'); un po' di codice $stopwatch->lap('pippo'); un altro po' di codice $event = $stopwatch->stop('pippo'); Le informazioni sulle frazioni sono memorizzate come "periodi" dell'evento. Per ottenere informazioni sulle frazioni, richiamare: Listing - $event->getperiods(); Oltre ai periodi, si possono ottenere informazioni utili dall'oggetto evento. Per esempio: Listing - $event->getcategory(); // Restituisce la categoria dell'evento $event->getorigin(); // Restituisce il tempo di inizio dell'evento, in millisecondi $event->ensurestopped(); // Ferma tutti i periodi ancora in corso $event->getstarttime(); // Restituisce il tempo di inizio del primo periodo $event->getendtime(); // Restituisce il tempo di inizio dell'ultimo periodo $event->getduration(); // Restituisce la durata dell'evento, inclusi tutti i periodi $event->getmemory(); // Restituisce l'utilizzo massimo di memoria di tutti i periodi Sezioni Le sezioni sono un modo per suddividere logicamente la linea temporale in gruppi. Si possono vedere come Symfony usa le sezioni per visualizzare il ciclo di vita del framework nel profilatore. Ecco un esempio di uso di base delle sezioni: generated on August, 0 Chapter : Il componente Stopwatch
334 Listing - $stopwatch = new Stopwatch(); $stopwatch->opensection(); $stopwatch->start('parsing_config_file', 'filesystem_operations'); $stopwatch->stopsection('routing'); $events = $stopwatch->getsectionevents('routing'); Si può riaprire una sezione chiusa, richiamando il metodo opensection() 0 e specificando l'id della sezione da riaprire: Listing - $stopwatch->opensection('routing'); $stopwatch->start('building_config_tree'); $stopwatch->stopsection('routing'); 0. generated on August, 0 Chapter : Il componente Stopwatch
335 Chapter Il componente Templating Il componente Templating fornisce tutti gli strumenti necessari per costruire un sistema di template. Fornisce un'infrastruttura per caricare file di template e, opzionalmente, monitorarne le modifiche. Fornisce anche l'implementazione concreta di un motore di template, usando PHP e strumenti aggiuntivi per l'escape e per la separazione dei template in blocchi e layout. Installazione Si può installare il componente in due modi: Installarlo tramite Composer (symfony/templating su Packagist ); Usare il repository ufficiale su Git ( ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Uso La classe PhpEngine è il punto di ingresso del componente. Ha bisogno di un analizzatore di nomi di template (TemplateNameParserInterface ), per convertire il nome di un template in un riferimento a un template (TemplateReferenceInterface ). Ha inoltre bisogno di un caricatore di template (LoaderInterface ), per trovare il template associato a un riferimento: generated on August, 0 Chapter : Il componente Templating
336 Listing - use Symfony\Component\Templating\PhpEngine; use Symfony\Component\Templating\TemplateNameParser; use Symfony\Component\Templating\Loader\FilesystemLoader; $loader = new FilesystemLoader( DIR.'/views/%name%'); $templating = new PhpEngine(new TemplateNameParser(), $loader); echo $templating->render('hello.php', array('firstname' => 'Fabien')); Listing - <!-- views/hello.php --> Ciao, <?php echo $firstname?>! Il metodo render() esegue il file views/hello.php e restituisce il testo di output. Il secondo parametro di render è un array di variabili da usare nel template. In questo esempio, il risultato sarà Ciao, Fabien!. I template saranno messi in cache all'interno della memoria del motore. Questo vuol dire che se si rende lo stesso template più volte nella stessa richiesta, il template sarà caricato un'unica volta dal filesystem. La variabile $view In tutti i template analizzati da PhpEngine si ha accesso a una misteriosa variabile, chiamata $view. Questa variabile contiene l'istanza corrente di PhpEngine. Questo vuol dire che si ha accesso a un sacco di metodi che rendono facile la vita. Includere template Il modo migliore per condividere una porzione di codice è creare un template che possa essere incluso da altri template. Siccome la variabile $view è un'istanza di PhpEngine, si può usare il metodo render (usato per rendere originariamente il template) all'interno del template, per rendere un altro template: Listing - <?php $names = array('fabien',...)?> <?php foreach ($names as $name) :?> <?php echo $view->render('hello.php', array('firstname' => $name))?> <?php endforeach?> Variabili globali A volte si ha bisogno di impostare una variabile che sia disponibile in tutti i template resi da un motore (come la variabile $app quando si usa il framework Symfony). Tali variabili possono essere impostate usando il metodo addglobal() e vi si può accedere nel template come normali variabili: Listing - $templating->addglobal('ga_tracking', 'UA-xxxxx-x');. generated on August, 0 Chapter : Il componente Templating
337 In un template: Listing - <p>il codice di tracking di google è: <?php echo $ga_tracking?></p> Le variabili globali non possono chiamarsi this o view, poiché tali nomi sono usati dal motore PHP. Le variabili globali possono essere sovrascritte da variabili locali nel template che abbiano lo stesso nome. Escape dell'output Quando si rendono delle variabili, probabilmente si vorrà un escape, in modo che il codice HTML o JavaScript non venga scritto nella pagina. In questo modo si prevengono attacchi come XSS. Per poterlo fare, usare il metodo escape() : Listing - <?php echo $view->escape($firstname)?> Per impostazione predefinita, il metodo escape() ipotizza che la variabile sia mostrata in un contesto HTML, Il secondo parametro dà la possibilità di cambiare tale contesto. Per esempio, per mostrare una variabile in JavaScript, usare il contesto js: Listing - <?php echo $view->escape($var, 'js')?> Il componente fornisce escape in HTML e JS. Si può registrare un escape personalizzato, usando il metodo setescaper() 0 : Listing - $templating->setescaper('css', function ($value) escape CSS ); return $escapedvalue; Aiutanti Il componente Templating può essere facilmente esteso, tramite aiutanti. Gli aiutanti sono oggetti PHP, che forniscono caratteristiche utili nel contesto di un template. Il componente ha due aiutanti predefiniti: Aiutante per gli asset Aiutante slots Prima di poterli usare, occorre registrare tali aiutanti, usando set() : Listing generated on August, 0 Chapter : Il componente Templating
338 use Symfony\Component\Templating\Helper\AssetsHelper; $templating->set(new AssetsHelper()); Aiutanti personalizzati Si può creare un proprio aiutante, creando una classe che implementi HelperInterface. Tuttavia, la maggior parte delle volte si estenderà Helper. La classe Helper ha un metodo obbligatorio: getname(). Restituisce il nome da usare per ottenere l'aiutante dall'oggetto $view. Creare un motore personalizzato Oltre a fornire un motore di template PHP, si può anche creare un proprio motore, usando il componente Templating. Per poterlo fare, creare una nuova classe che implementi EngineInterface. L'interfaccia richiede tre metodi: render($name, array $parameters = array()) - Rende un template exists($name) - Verifica se il template esiste supports($name) - Verifica se il template dato possa essere gestito dal motore. Usare più motori Si possono usare più motori contemporaneamente, usando la classe DelegatingEngine. Questa classe accetta una lista di motori e agisce come un normale motore di template. La sola differenza è che delega le chiamate a uno degli altri motori. Per scegliere quale motore usare per il template, viene usato il metodo EngineInterface::supports() 0. Listing -0 use Acme\Templating\CustomEngine; use Symfony\Component\Templating\PhpEngine; use Symfony\Component\Templating\DelegatingEngine; $templating = new DelegatingEngine(array( new PhpEngine(...), new CustomEngine(...), )); generated on August, 0 Chapter : Il componente Templating
339 Chapter Aiutante slots Molto spesso, i template in un progetto condividono alcuni elementi comuni, come i ben noti testata e piè di pagina. Usando questo aiutante, il codice statico HTML può essere inserito in un file di layout, insieme a degli "slot", che rappresentano le parti dinamiche, che cambieranno in base alle varie pagine. Tali slot saranno quindi riempiti da diversi template figli. In altre parole, il file di layout decora il template figlio. Mostrare gli slot Gli slot sono accessibili grazie all'aiutante slots ($view['slots']). Usare output() per mostrare il contenuto dello slot in quella posizione: Listing - 0 <!-- views/layout.php --> <!doctype html> <html> <head> <title> <?php $view['slots']->output('title', 'Titolo predefinito')?> </title> </head> <body> <?php $view['slots']->output('_content')?> </body> </html> Il primo parametro del metodo è il nome dello slot. Il metodo ha un secondo parametro, opzionale, che è il valore predefinito da usare nel caso in cui lo slot non sia disponibile. Lo slot _content è uno slot speciale, impostato da PhpEngine. Contiene il contenuto del sotto-template.. generated on August, 0 Chapter : Aiutante slots
340 Se si usa il componente da solo, assicurarsi di avere registrato SlotsHelper : Listing - use Symfony\Component\Templating\Helper\SlotsHelper; $templateengine->set(new SlotsHelper()); Estendere i template Il metodo extend() viene richiamato nel sotto-template, per impostare il suo template genitore. Quindi, si può usare $view['slots']->set() per impostare il contenuto dello slot. Tutto il contenuto non impostato esplicitamente in uno slot è nello slot _content. Listing - 0 <!-- views/page.php --> <?php $view->extend('layout.php')?> <?php $view['slots']->set('title', $page->title)?> <h> <?php echo $page->title?> </h> <p> <?php echo $page->body?> </p> Si possono avere livelli multipli di ereditarietà: un layout può estendere un altro layout. Per slot più grandi, c'è anche una sintassi estesa: Listing - <?php $view['slots']->start('title')?> Un grande ammontare di HTML <?php $view['slots']->stop()?> generated on August, 0 Chapter : Aiutante slots 0
341 Chapter 0 Aiutante per gli asset Lo scopo principale dell'aiutante per gli asset è quello di rendere un'applicazione più portabile, generando i percorsi degli asset: Listing 0- <link href="<?php echo $view['assets']->geturl('css/style.css')?>" rel="stylesheet"> <img src="<?php echo $view['assets']->geturl('images/logo.png')?>"> L'aiutante per gli asset può essere configurare per rendere percorsi in un CDN o per modificare i percorsi nel caso in cui gli asset si trovino in una sotto-cartella (p.e. Configurare i percorsi Per impostazione predefinita, l'aiutante per gli asset aggiungerà a ogni percorso una barra iniziale. Si può configurare questo comportamento, passando un percorso di base come primo parametro del costruttore: Listing 0- use Symfony\Component\Templating\Helper\AssetsHelper; $templateengine->set(new AssetsHelper('/pippo/pluto')); Ora, se si usa l'aiutante, il prefisso per ogni percorso sarà /pippo/pluto: Listing 0- <img src="<?php echo $view['assets']->geturl('images/logo.png')?>"> <!-- viene reso come: <img src="/foo/bar/images/logo.png"> --> generated on August, 0 Chapter 0: Aiutante per gli asset
342 URL assoluti Si può specificare un URL da usare, nel secondo parametro del costruttore: Listing 0- $templateengine->set(new AssetsHelper(null, ' Ora gli URL saranno resi come New in version.: Gli URL assoluti per le risorse sono stati introdotti in Symfony.. Si può anche usare un terzo parametro, per forzare un URL assoluto: Listing 0- <img src="<?php echo $view['assets']->geturl('images/logo.png', null, true)?>"> <!-- sarà reso come: <img src=" --> Se si imposta già un URL nel costruttore, l'uso del terzo parametro di geturl non avrà effetto sull'url generato. Versionamento Per evitare l'uso di risorse in cache, dopo l'aggiornamento di una vecchia risorsa, si possono usare le versioni, da aggiornare ogni volta che si rilascia un nuovo progetto. La versione può essere verificata come terzo parametro: Listing 0- $templateengine->set(new AssetsHelper(null, null, 'rad')); Ora, ogni URL avrà come suffisso?rad. Se si vuole un formato diverso, lo si può specificare come quarto argomento. Deve essere una stringa da usare in sprintf. Il primo parametro è il percorso e il secondo è la versione. Per esempio, %s?v=%s sarà reso come /images/logo.png?v=rad. New in version.: Gli URL versionati al volo per le risorse sono stati introdotti in Symfony.. Si può anche generare un URL versionato su una risorsa in base alle singole risorse, usando il quarto parametro dell'aiutante: Listing 0- <img src="<?php echo $view['assets']->geturl('images/logo.png', null, false, '.0')?>"> <!-- sarà reso come: <img src="/images/logo.png?v=.0"> --> Pacchetti multipli La generazione dei percorsi degli asset è gestita internamente da pacchetti. Il componente fornisce due pacchetti predefiniti:. generated on August, 0 Chapter 0: Aiutante per gli asset
343 PathPackage UrlPackage Si possono anche usare più pacchetti: Listing 0- use Symfony\Component\Templating\Asset\PathPackage; $templateengine->set(new AssetsHelper()); $templateengine->get('assets')->addpackage('images', new PathPackage('/images/')); $templateengine->get('assets')->addpackage('scripts', new PathPackage('/scripts/')); In questo modo l'aiutante degli asset userà tre pacchetti: quello predefinito, che usa come prefisso / (impostato dal costruttore), il pacchetto delle immagini, che usa /images/ e il pacchetto degli scritp, che usa /scripts/. Se si vuole cambiare il pacchetto predefinito, si può usare setdefaultpackage(). Si può specificare quale pacchetto si vuole usare nel secondo parametro di geturl() : Listing 0- <img src="<?php echo $view['assets']->geturl('foo.png', 'images')?>"> <!-- sarà reso come: <img src="/images/foo.png"> --> Pacchetti personalizzati Si possono creare i propri pacchetti, estendendo Package generated on August, 0 Chapter 0: Aiutante per gli asset
344 Chapter Il componente Translation Il componente Translation fornisce strumenti per internazionalizzare un'applicazione. Installazione Si può installare il componente in due modi: Installarlo tramite Composer (symfony/translation su Packagist ); Usare il repository ufficiale su Git ( ). Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Costruire il Translator Il punto di accesso principale al componente Translation è Translator.Prima di poterlo usare, occorre configurarlo e caricare i messaggi da tradurre (chiamati cataloghi di messaggi). Configurazione Il costruttore della classe Translator ha bisogno di un solo parametro: il locale. Listing - use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\MessageSelector; $translator = new Translator('fr_FR', new MessageSelector()); generated on August, 0 Chapter : Il componente Translation
345 Il locale impostato qui è quello predefinito da usare. Lo può ridefinire durante la traduzione delle stringhe. Il termine locale si riferisce più o meno a lingua e paese dell'utente. Può essere unq qualsiasi stringa usata da un'applicazione per gestire traduzioni e altre variazioni di formato (p.e. la valuta). Si raccomanda un codice ISO - della lingua, un trattino basso (_), quindi il codice ISO - alpha- del paese (p.e. fr_fr per francese/francia). Caricare i cataloghi di messaggi I messaggi sono memorizzati in cataloghi, all'interno della classe Translator. Un catalogo di messaggi è come un dizionario di traduzioni per uno specifico locale. Il componente Translation usa della classi Loader per caricare i cataloghi. Si possono caricare più risorse per lo stesso locale, saranno combinato in un unico catalogo. Il componente dispone di alcuni Loader, ma se ne possono creare altri. I Loader predefiniti sono: ArrayLoader - per caricare cataloghi da array PHP. CsvFileLoader - per caricare cataloghi da file CSV. IcuDatFileLoader - per caricare cataloghi da bundle di risorse. IcuResFileLoader - per caricare cataloghi da rundle di risorse. IniFileLoader 0 - per caricare cataloghi da file ini. MoFileLoader - per caricare cataloghi da file gettext. PhpFileLoader - per caricare cataloghi da file PHP. PoFileLoader - per caricare cataloghi da file gettext. QtFileLoader - per caricare cataloghi da file QT XML. XliffFileLoader - per caricare cataloghi da file Xliff. JsonFileLoader - per caricare cataloghi da file JSON. YamlFileLoader - per caricare cataloghi da file Yaml (richiede il componente Yaml). Tutti i Loader di file richiedono il componente Config. Si possono anche creare Loader personalizzati, nel caso in cui il formato non fosse già supportato da uno di quelli predefiniti. Per prima cosa, aggiungere uno o più Loader a Translator: Listing - $translator->addloader('array', new ArrayLoader()); generated on August, 0 Chapter : Il componente Translation
346 Il primo parametro è il nome con cui si può fare riferimento al Loader in Translator e il secondo parametro è un'istanza del Loader stesso. Successivamente, si possono aggiungere risorse, usando il Loader corretto. Caricare messaggi con ArrayLoader Si possono caricare messaggi richiamando addresource(). Il primo parametro è il nome del Loader (che era il primo parametro del metodo addloader), il secondo è la risorsa e il terzo è il locale: Listing - $translator->addresource('array', array( 'Hello World!' => 'Bonjour', ), 'fr_fr'); Caricare messaggi i caricatori di file Se si usa uno dei Loader di file, si dovrebbe usare anche il metodo addresource. L'unica differenza è che si dovrebbe mettere il percorso della risorsa del file come secondo parametro, invece di un array: Listing - $translator->addloader('yaml', new YamlFileLoader()); $translator->addresource('yaml', 'path/to/messages.fr.yml', 'fr_fr'); Il processo di traduzione Per tradurre effettivamente il messaggio, Translator usa un semplice processo: Carica un catalogo di messaggi tradotti dalle risorse di traduzione definite per locale (p.e. fr_fr). Carica anche i Locale predefiniti e li aggiunge al catalogo, se non esistono ancora. Il risultato finale è un grosso "dizionario" di traduzioni; Se il messaggio si trova nel catalogo, ne restituisce la traduzione. Altrimenti, restituisce il messaggio originale. Il processo inizia quando di richiama trans() o transchoice() 0. Quindi, Translator cerca la string nell'appropriato catalogo di messaggi e la restituisce (se esiste). Locale predefiniti Se il messaggio non si trova nel catalogo speficiato dal locale, Translator cercherà nei cataloghi dei locale predefiniti. Per esempio, se si prova a tradurre nel locale fr_fr:. Translator cerca prima la traduzione nel locale fr_fr;. Se non la trova, cerca la traduzione nel locale fr;. Se non la trova ancora, usa uno o più locale predefiniti, impostati esplicitamente. Per il terzo punto, i locale predefiniti possono essere impostati richiamando setfallbacklocale() : Listing - $translator->setfallbacklocales(array('en')); generated on August, 0 Chapter : Il componente Translation
347 Uso dei domini dei messaggi Come già visto, i file dei messaggi sono organizzati nei vari locale che traducono. I file dei messaggi possono anche essere ulteriormente organizzati in "domini". Il dominio è specificato nel quarto parametro del metodo addresource(). Il dominio predefinito è messages. Per esempio, si supponga che, per organizzarle meglio, le traduzioni siano suddivise in tre domini: messages, admin e navigation. La traduzione francese sarebbe caricata in questo modo: Listing - 0 $translator->addloader('xlf', new XliffFileLoader()); $translator->addresource('xlf', 'messages.fr.xlf', 'fr_fr'); $translator->addresource('xlf', 'admin.fr.xlf', 'fr_fr', 'admin'); $translator->addresource( 'xlf', 'navigation.fr.xlf', 'fr_fr', 'navigation' ); Quando si traducono stringhe che non sono nel dominio predefinito (messages), si deve specificare il dominio come terzo parametro di trans(): Listing - $translator->trans('symfony is great', array(), 'admin'); Symfony ora cercherà il messaggio nel dominio admin del locale specificato. Uso Leggere come usare il componente Translation in Uso di Translator. generated on August, 0 Chapter : Il componente Translation
348 Chapter Uso di Translator Si immagini di voler tradurra la stringa "Symfony is great" in francese: Listing - 0 use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\Loader\ArrayLoader; $translator = new Translator('fr_FR'); $translator->addloader('array', new ArrayLoader()); $translator->addresource('array', array( 'Symfony is great!' => 'J\'aime Symfony!', ), 'fr_fr'); echo $translator->trans('symfony is great!'); In questo esempio, il messaggio "Symfony is great!" sarà tradotto nel locale impostato nel costruttore (fr_fr), se il messaggio esiste in uno dei cataloghi dei messaggi. Segnaposto dei messaggi A volte, occorre tradurre un messaggio che contiene una variabile: Listing - $translated = $translator->trans('hello '.$name); echo $translated; Tuttavia, creare una traduzione per questa stringa è impossibile, perché il traduttore proverebbe a cercare il messaggio esatto, inclusa la parte variabile (p.e. "Hello Ryan" o "Hello Fabien"). Invece di scrivere una traduzione per ogni possibile occorrenza della variabile $name, si può sostituire la variabile con un "segnaposto": Listing - $translated = $translator->trans( generated on August, 0 Chapter : Uso di Translator
349 ); 'Hello %name%', array('%name%' => $name) echo $translated; Symfony ora cercherà di tradurre il messaggio grezzo (Hello %name%) e poi di sostituire i segnaposto con i rispettivi valori. La creazione di una traduzione si fa come in precedenza: Listing - 0 <?xml version=".0"?> <xliff version="." xmlns="urn:oasis:names:tc:xliff:document:."> <file source-language="en" datatype="plaintext" original="file.ext"> <body> <trans-unit id=""> <source>hello %name%</source> <target>bonjour %name%</target> </trans-unit> </body> </file> </xliff> I segnaposto posso essere composti a piacere, perché il messaggio viene ricostruito usando la funzione strtr function di PHP. Tuttavia, la forma %...% è quella raccomandata, per evitare problemi con Twig. Come visto, la creazione di una traduzione è un processo in due fasi:. Astrarre il messaggio da tradurre, processandolo tramite Translator.. Creare una traduzione per il messaggio in ogni locale che si desidera supportare. Il secondo passo si esegue creando cataloghi di messaggi, che definiscono le traduzioni per qualsiasi numero di diversi locale.. generated on August, 0 Chapter : Uso di Translator
350 Chapter Creare traduzioni L'atto di creazione dei file di traduzione è una parte importante della "localizzazione" (spesso abbreviata in L0n ). La traduzione dei file consiste in una serie di copppie id-traduzione per una dato dominio e locale. La sorgente è l'identificativo della singola traduzione e può essere il messaggio nel locale principale (p.e. "Symfony is great") dell'applicazione o un'identificativo univoco (p.e. symfony.great, vedere il riquadro sotto). I file di traduzione possono essere tradotti in vari formati, con XLIFF come formato raccomandato. Questi file sono analizzati da una delle classi Loader. Listing - 0 <?xml version=".0"?> <xliff version="." xmlns="urn:oasis:names:tc:xliff:document:."> <file source-language="en" datatype="plaintext" original="file.ext"> <body> <trans-unit id=""> <source>symfony is great</source> <target>j'aime Symfony</target> </trans-unit> <trans-unit id=""> <source>symfony.great</source> <target>j'aime Symfony</target> </trans-unit> </body> </file> </xliff>. generated on August, 0 Chapter : Creare traduzioni 0
351 Usare messaggi reali o parole chiave Questo esempio illustra le due diverse filosofie di creazione di messaggi da tradurre: Listing - $translator->trans('symfony is great'); $translator->trans('symfony.great'); Nel primo metodo, i messaggi sono scritti nella lingua del locale predefinito (in questo caso, inglese). Tali messaggi sono quindi usati come "id" durante la creazione di traduzioni. Nel secondo metodo, i messaggi sono in realtà "parole chiave", che portano l'idea del messaggio. Le parole chiave sono quindi usate come "id" per ogni traduzione. In questo c aso, occorre tradurre anche per il locale predefinito (quindi tradurre symfony.great in Symfony is great). Il secondo metodo è comodo, perché la chiave del messaggio non ha mai bisogno di essere cambiata, in ogni file di traduzione, se per esempio si decide di cambiare il messaggio in "Symfony is really great" nel locale predefinito. La scelta del metodo spetta allo sviluppatore, ma il formato "parola chiave" spesso è raccomandato. Inoltre, i formati di file php e yaml supportano id innestate, che evitano di ripetere molte volte la stessa parte degli id: Listing - symfony: is: great: Symfony is great amazing: Symfony is amazing has: bundles: Symfony has bundles user: login: Login I livelli multipli vengono appiattiti in una singola coppia id/traduzione, aggiungengo un punto (.) tra ogni livello, quindi l'esempio appena visto è equivalente al seguente: Listing - symfony.is.great: Symfony is great symfony.is.amazing: Symfony is amazing symfony.has.bundles: Symfony has bundles user.login: Login Pluralizzazione La pluralizzazione dei messaggi è un argomento difficile, con regole che possono essere molto complesse. Per esempio, questa è una rappresentazione matematica delle regole di pluralizzazione russe: Listing - (($number % 0 == ) && ($number % 00!= ))? 0 : ((($number % 0 >= ) && ($number % 0 <= ) && (($number % 00 < 0) ($number % 00 >= 0)))? : ); generated on August, 0 Chapter : Creare traduzioni
352 Listing -0 Come si può, vedere possono essere tre diverse forme plurali in russo, a cui si può dare indice 0,,. Per ciascuna forma, il plurale è diverso e quindi anche la traduzione è diversa. Quanto ha una traduzione ha diverse forme di pluralizzazione, si possono fornire tutte le forme, come stringa separata da barre ( ): 'There is one apple There are %count% apples' Per tradurre messaggi pluralizzati, usare il metodo transchoice() : Listing - $translator->transchoice( 'There is one apple There are %count% apples', 0, array('%count%' => 0) ); Il secondo parametro (0 in questo esempio), è il numero di oggetti descritti e viene usato per determinare la traduzione da usare e anche per popolare il segnaposto %count%. In base al numero fornito, il traduttore sceglie la forma corretta di plurale. In inglese, la maggior parte delle parole ha una forma singolare, quando c'è esattamente un oggetto, e una forma plurale, per tutti gli altri numeri (0,,...). Quindi, se count è, il traduttore userà la prima stringa (There is one apple) come traduzione. Altrimenti, userà There are %count% apples. Ecco la traduzione in francese: Listing - 'Il y a %count% pomme Il y a %count% pommes' Anche se la stringa sembra simile (è fatta di due sottostringhe separate da una barra), le regole francesi sono diverse: la prima forma (senza plurale) è usata quando count è 0 o. Quindi, il traduttore usera automaticamente la prima stringa (Il y a %count% pomme) quando count è 0 o. Ogni locale ha il suo insieme di regole, alcuni con fino a sei diverse forme plurali, con regole complesse in base a cui mappare i numeri alle forme plurali. Le regole sono semplici per inglese e francese, ma per il russo si potrebbe volere un suggerimento su quale regola corrisponda a quale stringa. Per aiutare i traduttori, si può, facoltativamente, assegnare un tag a ogni stringa: Listing - 'uno: There is one apple some: There are %count% apples' 'nessuno_o_uno: Il y a %count% pomme some: Il y a %count% pommes' I tag sono solo suggerimenti per il traduttore e non hanno effetto sulla logica usata per determinare la forma plurale da usare. I tag possono essere qualsiasi stringa descrittiva che finisca con duepunti (:). I tag inoltre non devono necessariamente essere uguali nel messaggio originale e in quello tradotto. Essendo i tag opzionali, il traduttore non li usa (il traduttore otterrà solo una stringa basata sulle loro posizioni nella stringa). Intervallo di pluralizzazione esplicito Il modo più facile per pluralizzare un messaggio è lasciare che il traduttore usi la logica interna per scegliere la stringa da usare in base al numero dato. A volte, sarà necessario maggior controllo o si vorrà. generated on August, 0 Chapter : Creare traduzioni
353 una traduzione diversa per casi specifici (per 0 o quanto il conteggio è negativo, per esempio). Per questi casi, si possono usare intervalli matematici espliciti: Listing - '0 There are no apples There is one apple ],] There are %count% apples [0,Inf] There are many apples' Gli intervalli seguono la notazione ISO -. La stringa vista sopra specifica quattro diversi intervalli: esattamente 0, esattamente, - e da 0 in poi. Si possono anche mischiare regole matematiche espliciti e regole standard. In questo caso, se il conteggio non corrisponde a un intervallo specifico, le regole standard hanno effetto dopo la rimozione delle regole esplicite: Listing - '0 There are no apples [0,Inf] There are many apples There is one apple a_few: There are %count% apples' Per esempio, per mela, sarà usata la regola standard There is one apple. Per - mele, sarà scelta la seconda regola standard, There are %count% apples. Un oggetto Interval può rappresentare un insieme finito di numeri: Listing -,,, Oppure numeri compresi tra due numeri: Listing - [, +Inf[ ]-,[ Il delimitatore sinistro può essere [ (inclusivo) o ] (esclusivo). Il delimitatore destro può essere [ (esclusivo) o ] (inclusivo). Oltre ai numeri, si possono usare -Inf e +Inf per l'infinito. Forzare il locale in Translator Durante la traduzione di un messaggio, Translator usa il locale specificato o il locale fallback, se necessario. Si può anche specificare manualmente il locale da usare: Listing - 0 $translator->trans( 'Symfony is great', array(), 'messages', 'fr_fr' ); $translator->transchoice( '0 There are no apples There is one apple ],Inf[ There are %count% apples', 0, array('%count%' => 0), 'messages', 'fr_fr' );. generated on August, 0 Chapter : Creare traduzioni
354 Chapter Aggiungere supporto per un formato personalizzato A volte, si ha l'esigenza di un formato personalizzato per i file di traduzione. Il componente Translation è abbastanza flessibile da supportarlo. Basta creare un caricatore (per caricare traduzioni) e, eventualmente, un esportatore (per esportarle). Si immagini di avere un formato personalizzato, in cui i messaggi di traduzione sono definiti usando una riga per ogni traduzione e parentesi intorno a chiave e messaggio. Un file di traduzione sarebbe simile a questo: Listing - (welcome)(accueil) (goodbye)(au revoir) (hello)(bonjour) Creare un caricatore personalizzato Per definire un caricatore personalizzato, che possa leggere questo tipo di file, si deve creare una nuova classe, che implementi LoaderInterface. Il metodo load() prende un nome di file e lo analizza, producendo un array. Quindi, crea il catalogo, che viene restituito: Listing - use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\Loader\LoaderInterface; class MyFormatLoader implements LoaderInterface public function load($resource, $locale, $domain = 'messages') $messages = array(); $lines = file($resource);. generated on August, 0 Chapter : Aggiungere supporto per un formato personalizzato
355 0 0 foreach ($lines as $line) if (preg_match('/\(([^\)]+)\)\(([^\)]+)\)/', $line, $matches)) $messages[$matches[]] = $matches[]; $catalogue = new MessageCatalogue($locale); $catalogue->add($messages, $domain); return $catalogue; Una volta creato, può essere usato, come ogni altro caricatore: Listing - use Symfony\Component\Translation\Translator; $translator = new Translator('fr_FR'); $translator->addloader('my_format', new MyFormatLoader()); $translator->addresource('my_format', DIR.'/translations/messages.txt', 'fr_fr'); echo $translator->trans('welcome'); Mostrerà "accueil". Creare un esportatore personalizzato Si può anche creare un esportatore personalizzato per un formato, il che è utile se si usano i comandi di estrazione. Per farlo, occorre creare una nuova classe, che implementi DumperInterface. Per scrivere il contenuto esportato in un file, estendendo la classe FileDumper farà risparmiare alcune righe: Listing - 0 use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\Dumper\FileDumper; class MyFormatDumper extends FileDumper protected function format(messagecatalogue $messages, $domain = 'messages') $output = ''; foreach ($messages->all($domain) as $source => $target) $output.= sprintf("(%s)(%s)\n", $source, $target); return $output; protected function getextension(). generated on August, 0 Chapter : Aggiungere supporto per un formato personalizzato
356 0 return 'txt'; Il metodo format() crea la stringa di output, che sarà usata dal metodo dump() della classe FileDumper per creare il file. L'esportatore può essere usato come ogni altro esportatore predefinito. Nell'esempio seguente, i messaggi di traduzione definiti nel file YAML sono esportati in un file di testo, con il formato personalizzato: Listing - use Symfony\Component\Translation\Loader\YamlFileLoader; $loader = new YamlFileLoader(); $catalogue = $loader->load( DIR. '/translations/messages.fr_fr.yml', 'fr_fr'); $dumper = new MyFormatDumper(); $dumper->dump($catalogue, array('path' => DIR.'/dumps'));. generated on August, 0 Chapter : Aggiungere supporto per un formato personalizzato
357 Chapter Il componente VarDumper (TODO da tradurre...) generated on August, 0 Chapter : Il componente VarDumper
358 Chapter Utilizzo avanzato del componente VarDumper (TODO da tradurre...) generated on August, 0 Chapter : Utilizzo avanzato del componente VarDumper
359 Chapter Il componente YAML Il componente YAML carica ed esporta file YAML. Che cos'è? Il componente YAML di Symfony analizza stringhe YAML da convertire in array PHP. È anche in grado di convertire array PHP in stringhe YAML. YAML, YAML Ain't Markup Language, è uno standard amichevole di serializzazione di dati per tutti i linguaggi di programmazione. YAML è un ottimo formato per i file di configurazione. I file YAML sono espressivi quanto i file XML e leggibili quanto i file INI. Il componente YAML di Symfony implementa un sottoinsieme scelto di caratteristiche definite nella versione. della specifica YAML. Si può sapere di più sul componente Yaml in Il formato YAML. Installazione Si può installare il componente in due modi: Installarlo tramite Composer (symfony/yaml su Packagist ); Usare il repository ufficiale su Git ( ) generated on August, 0 Chapter : Il componente YAML
360 Quindi, richiedere il file vendor/autoload.php per abilitare il meccanismo di auto-caricamento fornito da Composer. Altrimenti, l'applicazione non sarà in grado di trovare le classi di questo componente di Symfony. Perché? Veloce Uno degli scopi di YAML è trovare il giusto rapporto tra velocità e caratteristiche. Supporta proprio la caratteristica necessaria per gestire i file di configurazione. Alcune mancanze da notare: direttive documentali, messaggi citati multiriga, insiemi di blocchi compatti e file multidocumento. Analizzatore reale Dispone di un analizzatore reale, capace di analizzare un grande sotto-insieme della specifica YAML, per tutte le necessità di configurazione. Significa anche che l'analizzatore è molto robusto, facile da capire e facile da estendere. Messaggi di errore chiari Ogni volta che si ha un problema di sintassi con un proprio file YAML, la libreria mostra un utile messaggio di errore, con il nome del file e il numero di riga in cui il problema si è verificato. Questo facilita parecchio le operazioni di debug. Supporto per l'esportazione È anche in grado di esportare array PHP in YAML con supporto agli oggetti e configurazione in linea per output eleganti. Tipi supportati Supporta la maggior parte dei tipi di YAML, come date, interi, ottali, booleani e molto altro... Pieno supporto alla fusione di chiavi Pieno supporto per riferimenti, alias e piena fusione di chiavi. Non occorre ripetersi usando riferimenti a bit comuni di configurazione. Usare il componente YAML di Symfony Il componente YAML di Symfony è molto semplice e consiste di due classi principali: una analizza le stringhe YAML (Parser ) e l'altra esporta un array PHP in una stringa YAML (Dumper ). Sopra queste classi, la classe Yaml funge da involucro leggero, il che semplifica gli usi più comuni. Leggere file YAML Il metodo parse() analizza una stringa YAML e la converte in un array PHP: generated on August, 0 Chapter : Il componente YAML 0
361 Listing - use Symfony\Component\Yaml\Parser; $yaml = new Parser(); $valore = $yaml->parse(file_get_contents('/percorso/del/file.yml')); Se si verifica un errore durante l'analizi, l'analizzatore lancia un'eccezione ParseException, che indica il tipo di errore e la riga della stringa YAML originale in cui l'errore si è verificato: Listing - use Symfony\Component\Yaml\Exception\ParseException; try $valore = $yaml->parse(file_get_contents('/percorso/del/file.yml')); catch (ParseException $e) printf("impossibile analizzare la stringa YAML: %s", $e->getmessage()); Poiché l'analizzatore è rientrante, si può usare lo stesso oggetto analizzatore per caricare diverse stringhe YAML. Quando si carica un file YAML, a volte è meglio usare il metodo involucro parse() 0 : Listing - use Symfony\Component\Yaml\Yaml; $yaml = Yaml::parse(file_get_contents('/percorso/del/file.yml')); Il metodo statico parse() accetta una stringa YAML o un file contenente YAML. Internamente, richiama il metodo parse(), ma migliora gli errori, nel caso qualcosa vada storto, aggiungendo il nome del file al messaggio. Essendo attualmente possibile passare un nome di file a questo metodo, si deve validare l'input prima. Passare un nome di file è deprecato in Symfony. e sarà rimosso in Symfony.0. Scrivere file YAML Il metodo dump() esporta un array PHP nella corrispondente rappresentazione YAML: Listing - use Symfony\Component\Yaml\Dumper; $array = array( 'foo' => 'bar', 'bar' => array('foo' => 'bar', 'bar' => 'baz'), ); generated on August, 0 Chapter : Il componente YAML
362 0 $dumper = new Dumper(); $yaml = $dumper->dump($array); file_put_contents('/percorso/del/file.yml', $yaml); Ovviamente, l'esportatore YAML non è in grado di esportare risorse. Inoltre, anche se l'esportatore è in grado di esportare oggetti PHP, la caratteristica è considerata come non supportata. Se si verifica un errore durante l'esportazione, l'esportatore lancia un'eccezione DumpException. Se si ha bisogno di esportare un solo array, si può usare come scorciatoia il metodo statico dump() : Listing - use Symfony\Component\Yaml\Yaml; $yaml = Yaml::dump($array, $inline); Il formato YAML supporta due tipi di rappresentazioni di array, quello espanso e quello in linea. Per impostazione predefinita, l'esportatore usa la rappresentazione in linea: Listing - foo: bar, bar: foo: bar, bar: baz Il secondo parametro del metodo dump() rappresentazione espansa a quella in linea: personalizza il livello in cui l'output cambia dalla Listing - echo $dumper->dump($array, ); Listing - foo: bar bar: foo: bar, bar: baz Listing - echo $dumper->dump($array, ); Listing -0 foo: bar bar: foo: bar bar: baz generated on August, 0 Chapter : Il componente YAML
363 Chapter Il formato YAML Secondo il sito ufficiale di YAML, YAML è "uno standard amichevole di serializzazione dei dati per tutti i linguaggi di programmazione". Anche se il formato YAML può descrivere strutture di dati annidate in modo complesso, questo capitolo descrive solo l'insieme minimo di caratteristiche per usare YAML come formato per i file di configurazione. YAML è un semplice linguaggio che descrive dati. Come PHP, ha una sintassi per tipi semplici, come stringhe, booleani, numeri a virgola mobile o interi. Ma, diversamente da PHP, distingue tra array (sequenze) e hash (mappature). Scalari La sintassi per gli scalari è simile a quella di PHP. Stringhe In YAML, si possono inserire stringhe tra apici singoli o doppi. In alcuni casi, le stringhe possono non avere apici: Listing - Una stringa in YAML 'Una stringa in YAML tra apici singoli' "Una stringa in YAML tra apici doppi" Gli apici sono utili quando una stringa inizia o finisce con spazi significativi, perché alle stringhe senza apici vengono tolti gli spazi iniziali e finali. Gli apici sono obbligatori quando la stringa contiene caratteri speciali o riservati. Usando apici singoli, un apice singolo interno deve essere raddoppiato, per l'escape: Listing -. generated on August, 0 Chapter : Il formato YAML
364 'Un apice singolo '' in una stringa tra apici singoli' Le stringhe contenenti uno dei caratteri seguenti vanno tra apici. Sebbene si possano usare gli apici doppi, per questi caratteri è più conveniente usare apici singoli, perché evitano di dover aggiungere un \: :,,, [, ],,, &, *, #,?,, -, <, >, =,!, \` Lo stile ad apici doppi fornisce un modo per esprimere stringhe arbitrarie, usando \ per l'escape di caratteri e sequenze. Per esempio, è molto utile quando occorre inserire un \n oppure un carattere unicode. Listing - "Una stringa in YAML tra apici doppi\n" Se la stringa contiene uno dei seguenti caratteri di controllo, deve avere apici doppi: \0, \x0, \x0, \x0, \x0, \x0, \x0, \a, \b, \t, \n, \v, \f, \r, \x0e, \x0f, \x0, \x, \x, \x, \x, \x, \x, \x, \x, \x, \xa, \e, \xc, \xd, \xe, \xf, \N, \_, \L, \P Infine, ci sono altri casi in cui le stringhe vanno tra apici, non importa se singoli o doppi: Quando la stringa è true o false (altrimenti viene trattata come booleano); Quando la stringa è null o ~ (altrimenti viene trattata come null); Quando la stringa è un numero intero (p.e.,, ecc.), a virgola mobile (p.e..,.) o esponenziale (p.e. e, ecc.) (altrimenti viene trattata come valore numerico); Quando la stringa è una data (p.e. 0--) (altrimenti viene convertita automaticamente in un timestamp Unix). Quando una stringa contiene degli a capo, si può usare lo stile letterale, indicato dalla barra verticale ( ), per indicare che la stringa si estende su diverse righe. Nei letterali, gli a capo sono preservati: Listing - \/ / \/ / / In alternativa, le stringhe possono essere scritte con lo stile avvolto, denotato da >, in cui gli a capo sono sostituiti da uno spazio: Listing - > Questa è una frase molto lunga che si espande per diverse righe in YAML ma che sarà resa come una stringa senza rimandi a capo. Si notino i due spazi prima di ogni riga nell'esempio qui sopra. Non appariranno nella stringa PHP risultante. Numeri Listing - generated on August, 0 Chapter : Il formato YAML
365 # un intero Listing - # un ottale 0 Listing - # un esadecimale 0xC Listing - # un numero a virgola mobile. Listing -0 # un esponenziale.e+ Listing - # infinito.inf Null Null in YAML può essere espresso con null o con ~. Booleani I booleani in YAML sono espressi con true e false. Date YAML usa lo standard ISO-0 per esprimere le date: Listing t::.0-0:00 Listing - # data semplice 00-- Insiemi Un file YAML è usato raramente per descrivere semplici scalari. La maggior parte delle volte, descrive un insieme. Un insieme può essere una sequenza o una mappatura di elementi. Sia le sequenze che le mappature sono convertite in array PHP. Le sequenze usano un trattino, seguito da uno spazio: Listing - generated on August, 0 Chapter : Il formato YAML
366 - PHP - Perl - Python Il file YAML qui sopra equivale al seguente codice PHP: Listing - array('php', 'Perl', 'Python'); Le mappature usano un due punti, seguito da uno spazio (: ) per marcare ogni coppia chiave/valore: Listing - PHP:. MySQL:. Apache:..0 che equivale a questo codice PHP: Listing - array('php' =>., 'MySQL' =>., 'Apache' => '..0'); In una mappatura, una chiave può essere un qualsiasi scalare valido. Il numero di spazi tra i due punti e il valore non è significativo: Listing - PHP:. MySQL:. Apache:..0 YAML usa un'indentazione con uno o più spazi per descrivere insiemi annidati: Listing - "symfony.0": PHP:.0 Propel:. "symfony.": PHP:. Propel:. Lo YAML qui sopra equivale al seguente codice PHP: Listing -0 0 array( 'symfony.0' => array( 'PHP' =>.0, 'Propel' =>., ), 'symfony.' => array( 'PHP' =>., 'Propel' =>., ), ); C'è una cosa importante da ricordare quando si usano le indentazioni in un file YAML: le indentazioni devono essere fatte con uno o più spazi, ma mai con tabulazioni. generated on August, 0 Chapter : Il formato YAML
367 Si possono annidare sequenze e mappature a volontà: Listing - 'Capitolo ': - Introduzione - Tipi di eventi 'Capitolo ': - Introduzione - Aiutanti YAML può anche usare stili fluenti per gli insiemi, usando indicatori espliciti invece che le intendantazioni, per denotare il livello. Una sequenza può essere scritta come lista separata da virgole in parentesi quadre ([]): Listing - [PHP, Perl, Python] Una mappatura può essere scritta come lista separata da virgole di chiavi/valori tra parentesi graffe (): Listing - PHP:., MySQL:., Apache:..0 Si possono mescolare gli stili, per ottenere una migliore leggibilità: Listing - 'Chapter ': [Introduzione, Tipi di eventi] 'Chapter ': [Introduzione, Aiutanti] Listing - "symfony.0": PHP:.0, Propel:. "symfony.": PHP:., Propel:. Commenti Si possono aggiungere commenti in YAML, usando come prefisso un cancelletto (#): Listing - # Commento su una riga "symfony.0": PHP:.0, Propel:. # Commento a fine riga "symfony.": PHP:., Propel:. I commenti sono semplicemente ignorati dall'analizzatore YAML e non necessitano di indentazione in base al livello di annidamento di un insieme. generated on August, 0 Chapter : Il formato YAML
368
369
The Best Practices Book Version: 2.5
The Best Practices Book Version: 2.5 The Best Practices Book (2.5) This work is licensed under the Attribution-Share Alike 3.0 Unported license (http://creativecommons.org/ licenses/by-sa/3.0/). You are
Capitolo 4 Pianificazione e Sviluppo di Web Part
Capitolo 4 Pianificazione e Sviluppo di Web Part Questo capitolo mostra come usare Microsoft Office XP Developer per personalizzare Microsoft SharePoint Portal Server 2001. Spiega come creare, aggiungere,
I file di dati. Unità didattica D1 1
I file di dati Unità didattica D1 1 1) I file sequenziali Utili per la memorizzazione di informazioni testuali Si tratta di strutture organizzate per righe e non per record Non sono adatte per grandi quantità
Progettazione : Design Pattern Creazionali
Progettazione : Design Pattern Creazionali Alessandro Martinelli [email protected] 30 Novembre 2010 Progettazione : Design Pattern Creazionali Aspetti generali dei Design Pattern Creazionali
Modulo 4 Il pannello amministrativo dell'hosting e il database per Wordpress
Copyright Andrea Giavara wppratico.com Modulo 4 Il pannello amministrativo dell'hosting e il database per Wordpress 1. Il pannello amministrativo 2. I dati importanti 3. Creare il database - Cpanel - Plesk
Acronis License Server. Manuale utente
Acronis License Server Manuale utente INDICE 1. INTRODUZIONE... 3 1.1 Panoramica... 3 1.2 Politica della licenza... 3 2. SISTEMI OPERATIVI SUPPORTATI... 4 3. INSTALLAZIONE DI ACRONIS LICENSE SERVER...
Database. Si ringrazia Marco Bertini per le slides
Database Si ringrazia Marco Bertini per le slides Obiettivo Concetti base dati e informazioni cos è un database terminologia Modelli organizzativi flat file database relazionali Principi e linee guida
Esercizi di JavaScript
Esercizi di JavaScript JavaScript JavaScript é un linguaggio di programmazione interpretato e leggero, creato dalla Netscape. E' presente a patire da Netscape 2 in tutti i browser ed é dunque il linguaggio
Funzioni in C. Violetta Lonati
Università degli studi di Milano Dipartimento di Scienze dell Informazione Laboratorio di algoritmi e strutture dati Corso di laurea in Informatica Funzioni - in breve: Funzioni Definizione di funzioni
11/02/2015 MANUALE DI INSTALLAZIONE DELL APPLICAZIONE DESKTOP TELEMATICO VERSIONE 1.0
11/02/2015 MANUALE DI INSTALLAZIONE DELL APPLICAZIONE DESKTOP TELEMATICO VERSIONE 1.0 PAG. 2 DI 38 INDICE 1. PREMESSA 3 2. SCARICO DEL SOFTWARE 4 2.1 AMBIENTE WINDOWS 5 2.2 AMBIENTE MACINTOSH 6 2.3 AMBIENTE
Report WordPress plugin di Konora v 0.7
` Report WordPress plugin di Konora v 0.7 Konora ltd 4321 Somewhere Street City, State ZIP phone 555-555-5555 fax 555-555-5555 [email protected] website.com Indice Indice 2 Recuperare il codice del circolo
ATOLLO BACKUP GUIDA INSTALLAZIONE E CONFIGURAZIONE
ATOLLO BACKUP GUIDA INSTALLAZIONE E CONFIGURAZIONE PREMESSA La presente guida è da considerarsi come aiuto per l utente per l installazione e configurazione di Atollo Backup. La guida non vuole approfondire
Per chi ha la Virtual Machine: avviare Grass da terminale, andando su Applicazioni Accessori Terminale e scrivere grass
0_Iniziare con GRASS Avvio di Grass e creazione della cartella del Database di GRASS Per chi ha la Virtual Machine: avviare Grass da terminale, andando su Applicazioni Accessori Terminale e scrivere grass
Creare un sito Multilingua con Joomla 1.6
Creare un sito Multilingua con Joomla 1.6 Istruzioni Vai a: navigazione, ricerca Testo originale: http://docs.joomla.org/language_switcher_tutorial_for_joomla_1.6 Questa guida è valida sia per Joomla 1.6
Spazio Commerciale. Le tue vendite, il nostro successo. Manuale Operativo. Guida inserimento articoli tramite Area di amministrazione.
Manuale Operativo Guida inserimento articoli tramite Area di amministrazione Pagina 1 di 8 Indice Generale 1. Sommario 2. Introduzione 3. Glossario 4. Accesso all'interfaccia 5. Icone e funzionalità 5.1.
Il sofware è inoltre completato da una funzione di calendario che consente di impostare in modo semplice ed intuitivo i vari appuntamenti.
SH.MedicalStudio Presentazione SH.MedicalStudio è un software per la gestione degli studi medici. Consente di gestire un archivio Pazienti, con tutti i documenti necessari ad avere un quadro clinico completo
Servizi Remoti. Servizi Remoti. TeamPortal Servizi Remoti
20120300 INDICE 1. Introduzione... 3 2. Consultazione... 4 2.1 Consultazione Server Fidati... 4 2.2 Consultazione Servizi Client... 5 2.3 Consultazione Stato richieste... 5 3. Amministrazione... 6 3.1
Istruzioni di installazione di IBM SPSS Modeler Text Analytics (licenza per sito)
Istruzioni di installazione di IBM SPSS Modeler Text Analytics (licenza per sito) Le seguenti istruzioni sono relative all installazione di IBM SPSS Modeler Text Analytics versione 15 mediante un licenza
FtpZone Guida all uso Versione 2.1
FtpZone Guida all uso Versione 2.1 La presente guida ha l obiettivo di spiegare le modalità di utilizzo del servizio FtpZone fornito da E-Mind Srl. All attivazione del servizio E-Mind fornirà solamente
Esercizi su. Funzioni
Esercizi su Funzioni ๒ Varie Tracce extra Sul sito del corso ๓ Esercizi funz_max.cc funz_fattoriale.cc ๔ Documentazione Il codice va documentato (commentato) Leggibilità Riduzione degli errori Manutenibilità
Registratori di Cassa
modulo Registratori di Cassa Interfacciamento con Registratore di Cassa RCH Nucleo@light GDO BREVE GUIDA ( su logiche di funzionamento e modalità d uso ) www.impresa24.ilsole24ore.com 1 Sommario Introduzione...
PHP e MySQL. Guida scaricata da www.webstyling.it
Home -> Manuali & Tutorials -> Guida PHP PHP e MySQL E' possibile realizzare delle applicazioni in php appoggiandosi ad un database, quale ad esempio MySQL. Con le novità introdotte ai tempi di MySQL 4.1
NOZIONI BASE SHELL E SCRIPT LINUX
NOZIONI BASE SHELL E SCRIPT LINUX Aggiornato al 11 gennaio 2006 Ermes ZANNONI ([email protected]) (http://www.zannoni.to.it) Indice : 1. Introduzione 2. La Shell 2.1 Comandida Shell 2.1.1 File e directory
Il calendario di Windows Vista
Il calendario di Windows Vista Una delle novità introdotte in Windows Vista è il Calendario di Windows, un programma utilissimo per la gestione degli appuntamenti, delle ricorrenze e delle attività lavorative
File Server Resource Manager (FSRM)
File Server Resource Manager (FSRM) di Nicola Ferrini MCT MCSA MCSE MCTS MCITP Introduzione FSRM, File Server Resource Manager, è un ruolo opzionale che può essere installato in Windows Server 2008 nel
Progetto di Ingegneria del Software 2. SWIMv2
Progetto di Ingegneria del Software 2 2012/2013 SWIMv2 Guida al Testing Docente: Prof. Luca Mottola Davide Brambilla Antonio Caputo Paolo Caputo 1 Indice 1 Introduzione 1.1 Materiale fornito................................
FtpZone Guida all uso
FtpZone Guida all uso La presente guida ha l obiettivo di spiegare le modalità di utilizzo del servizio FtpZone fornito da E-Mind Srl. All attivazione del servizio E-Mind fornirà solamente un login e password
Manuale Amministratore Legalmail Enterprise. Manuale ad uso degli Amministratori del Servizio Legalmail Enterprise
Manuale Amministratore Legalmail Enterprise Manuale ad uso degli Amministratori del Servizio Legalmail Enterprise Pagina 2 di 16 Manuale Amministratore Legalmail Enterprise Introduzione a Legalmail Enterprise...3
RISOLUTORE AUTOMATICO PER SUDOKU
RISOLUTORE AUTOMATICO PER SUDOKU Progetto Prolog - Pierluigi Tresoldi 609618 INDICE 1.STORIA DEL SUDOKU 2.REGOLE DEL GIOCO 3.PROGRAMMAZIONE CON VINCOLI 4.COMANDI DEL PROGRAMMA 5.ESEMPI 1. STORIA DEL SUDOKU
SOSEBI PAPERMAP2 MODULO WEB MANUALE DELL UTENTE
SOSEBI PAPERMAP2 MODULO WEB MANUALE DELL UTENTE S O. S E. B I. P R O D O T T I E S E R V I Z I P E R I B E N I C U L T U R A L I So.Se.Bi. s.r.l. - via dell Artigianato, 9-09122 Cagliari Tel. 070 / 2110311
FPf per Windows 3.1. Guida all uso
FPf per Windows 3.1 Guida all uso 3 Configurazione di una rete locale Versione 1.0 del 18/05/2004 Guida 03 ver 02.doc Pagina 1 Scenario di riferimento In figura è mostrata una possibile soluzione di rete
Organizzazione degli archivi
COSA E UN DATA-BASE (DB)? è l insieme di dati relativo ad un sistema informativo COSA CARATTERIZZA UN DB? la struttura dei dati le relazioni fra i dati I REQUISITI DI UN DB SONO: la ridondanza minima i
Automatizzare i compiti ripetitivi. I file batch. File batch (1) File batch (2) Visualizzazione (2) Visualizzazione
Automatizzare i compiti ripetitivi I file batch Anno accademico 2000-01 1 Spesso capita di dover eseguire ripetutatmente una data sequenza di comandi Introdurli uno a uno da tastiera è un processo lento
Corso di Amministrazione di Reti A.A. 2002/2003
Struttura di Active Directory Corso di Amministrazione di Reti A.A. 2002/2003 Materiale preparato utilizzando dove possibile materiale AIPA http://www.aipa.it/attivita[2/formazione[6/corsi[2/materiali/reti%20di%20calcolatori/welcome.htm
Product Shipping Cost Guida d'installazione ed Utilizzo
Guida d'installazione ed Utilizzo Installazione Per installare il modulo è sufficiente copiare la cartella app del pacchetto del modulo nella cartella principale dell'installazione di Magento dove è già
I TUTORI. I tutori vanno creati la prima volta seguendo esclusivamente le procedure sotto descritte.
I TUTORI Indice Del Manuale 1 - Introduzione al Manuale Operativo 2 - Area Tutore o Area Studente? 3 - Come creare tutti insieme i Tutori per ogni alunno? 3.1 - Come creare il secondo tutore per ogni alunno?
1.0 GUIDA PER L UTENTE
1.0 GUIDA PER L UTENTE COMINCIA FACILE Una volta effettuato il login vi troverete nella pagina Amministrazione in cui potrete creare e modificare le vostre liste. Una lista è semplicemnte un contenitore
Definire all'interno del codice un vettore di interi di dimensione DIM, es. int array[] = {1, 5, 2, 4, 8, 1, 1, 9, 11, 4, 12};
ESERCIZI 2 LABORATORIO Problema 1 Definire all'interno del codice un vettore di interi di dimensione DIM, es. int array[] = {1, 5, 2, 4, 8, 1, 1, 9, 11, 4, 12}; Chiede all'utente un numero e, tramite ricerca
Configurazione avanzata di IBM SPSS Modeler Entity Analytics
Configurazione avanzata di IBM SPSS Modeler Entity Analytics Introduzione I destinatari di questa guida sono gli amministratori di sistema che configurano IBM SPSS Modeler Entity Analytics (EA) in modo
Che cos'è un modulo? pulsanti di opzione caselle di controllo caselle di riepilogo
Creazione di moduli Creazione di moduli Che cos'è un modulo? Un elenco di domande accompagnato da aree in cui è possibile scrivere le risposte, selezionare opzioni. Il modulo di un sito Web viene utilizzato
Sistema operativo. Sommario. Sistema operativo...1 Browser...1. Convenzioni adottate
MODULO BASE Quanto segue deve essere rispettato se si vuole che le immagini presentate nei vari moduli corrispondano, con buona probabilità, a quanto apparirà nello schermo del proprio computer nel momento
Licenza per sito Manuale dell amministratore
Licenza per sito Manuale dell amministratore Le seguenti istruzioni sono indirizzate agli amministratori di siti con un licenza per sito per IBM SPSS Modeler 15. Questa licenza consente di installare IBM
I Codici Documento consentono di classificare le informazioni e di organizzare in modo logico l archiviazione dei file.
Archivia Modulo per l acquisizione, l archiviazione e la consultazione di informazioni Il modulo Archivia permette l acquisizione, l archiviazione e la consultazione di informazioni (siano esse un immagine,
Mon Ami 3000 Varianti articolo Gestione di varianti articoli
Prerequisiti Mon Ami 3000 Varianti articolo Gestione di varianti articoli L opzione Varianti articolo è disponibile per le versioni Azienda Light e Azienda Pro e include tre funzionalità distinte: 1. Gestione
Manuale di Blogilo. Mehrdad Momeny Traduzione e revisione del documento: Valter Mura
Mehrdad Momeny Traduzione e revisione del documento: Valter Mura 2 Indice 1 Introduzione 5 2 Uso di Blogilo 6 2.1 Primi passi.......................................... 6 2.2 Configurazione di un blog.................................
Registrazione nuovo utente. Per registrare un nuovo utente cliccare sul link Registrazione
Manuale Gedos 2 Indice Indice... 3 Il Portale... 4 Registrazione nuovo utente... 5 Primo Logon... 8 Registrazione a Gedos... 9 Accesso ai Servizi... 11 Gestione Donatori... 12 Inserimento nuovo donatore...
CREAZIONE DI UN DATABASE E DI TABELLE IN ACCESS
CONTENUTI: CREAZIONE DI UN DATABASE E DI TABELLE IN ACCESS Creazione database vuoto Creazione tabella Inserimento dati A) Creazione di un database vuoto Avviamo il programma Microsoft Access. Dal menu
ImporterONE Export Plugin Magento
ImporterONE Export Plugin Magento Indice generale 1 INTRODUZIONE...2 2 INSTALLAZIONE DEL PLUGIN...2 3 CONFIGURAZIONE...4 4 OPERAZIONI PRELIMINARI...11 1-10 1 INTRODUZIONE Questo plugin di esportazione
Sviluppata da: Lo Russo - Porcelli Pag. 1 di 6 6FRSR utilizzare il DBMS Postgresql per imparare il linguaggio SQL.
Pag. 1 di 6 6FRSR utilizzare il DBMS Postgresql per imparare il linguaggio SQL. 2ELHWWLYL GD UDJJLXQJHUH SHU JOL VWXGHQWL alla fine dell esercitazione gli studenti dovranno essere in grado di: 1. utilizzare
PULSANTI E PAGINE Sommario PULSANTI E PAGINE...1
Pagina 1 Sommario...1 Apertura...2 Visualizzazioni...2 Elenco...2 Testo sul pulsante e altre informazioni...3 Comandi...3 Informazioni...4 Flow chart...5 Comandi...6 Pulsanti Principali e Pulsanti Dipendenti...6
Uso di JUnit. Fondamenti di informatica Oggetti e Java. JUnit. Luca Cabibbo. ottobre 2012
Fondamenti di informatica Oggetti e Java ottobre 2012 1 JUnit JUnit è uno strumento per assistere il programmatore Java nel testing JUnit consente di scrivere test di oggetti e classi Java i test sono
Modulo 4: Ereditarietà, interfacce e clonazione
Modulo 4: Ereditarietà, interfacce e clonazione Argomenti Trattati: Classi, Superclassi e Sottoclassi Ereditarietà Ereditarietà ed Attributi Privati Override super Ereditarietà e Costruttori Polimorfismo
per immagini guida avanzata Organizzazione e controllo dei dati Geometra Luigi Amato Guida Avanzata per immagini excel 2000 1
Organizzazione e controllo dei dati Geometra Luigi Amato Guida Avanzata per immagini excel 2000 1 Il raggruppamento e la struttura dei dati sono due funzioni di gestione dati di Excel, molto simili tra
Prova di Laboratorio di Programmazione
Prova di Laboratorio di Programmazione 6 febbraio 015 ATTENZIONE: Non è possibile usare le classi del package prog.io del libro di testo. Oltre ai metodi richiesti in ciascuna classe, è opportuno implementare
Sistema Operativo di un Router (IOS Software)
- Laboratorio di Servizi di Telecomunicazione Sistema Operativo di un Router (IOS Software) Slide tratte da Cisco Press CCNA Instructor s Manual ed elaborate dall Ing. Francesco Immè IOS Un router o uno
teamspace TM Sincronizzazione con Outlook
teamspace TM Sincronizzazione con Outlook Manuale teamsync Versione 1.4 * teamspace è un marchio registrato di proprietà della 5 POINT AG ** Microsoft Outlook è un marchio registrato della Microsoft Corporation
MANUALE PARCELLA FACILE PLUS INDICE
MANUALE PARCELLA FACILE PLUS INDICE Gestione Archivi 2 Configurazioni iniziali 3 Anagrafiche 4 Creazione prestazioni e distinta base 7 Documenti 9 Agenda lavori 12 Statistiche 13 GESTIONE ARCHIVI Nella
DURC Client 4 - Guida configurazione Firma Digitale. DURC Client 4.1.7
DURC Client 4.1.7 Guida configurazione firma digitale Attenzione: Per poter utilizzare la firma digitale con il Durc Client dalla versione 4.1.7 e successive è necessario riconfigurare la procedura di
E-mail: [email protected]. Gestione Filtri. InfoBusiness 2.8 Gestione Filtri Pag. 1/ 11
Gestione Filtri InfoBusiness 2.8 Gestione Filtri Pag. 1/ 11 INDICE Indice...2 1. GESTIONE DEI FILTRI...3 1.1. Filtri fissi...3 1.2. Filtro parametrico...5 1.3. Funzione di ricerca...6 2. CONTESTI IN CUI
Libero Emergency PC. Sommario
Emergenza PC (Garantisce le funzionalità di base delle operazioni di prestito e restituzione in caso di problemi tecnici sulla linea o di collegamento con il server) Sommario 1. Emergency PC...2 2. Iniziare
STRUMENTI DI PRESENTAZIONE MODULO 6
STRUMENTI DI PRESENTAZIONE MODULO 6 2012 A COSA SERVE POWER POINT? IL PROGRAMMA NASCE PER LA CREAZIONE DI PRESENTAZIONI BASATE SU DIAPOSITIVE (O LUCIDI) O MEGLIO PER PRESENTARE INFORMAZIONI IN MODO EFFICACE
Istruzioni per la configurazione di IziOzi
Istruzioni per la configurazione di IziOzi Installazione L'applicazione si può installare da qualunque dispositivo Android a partire dalla versione 4.1 con la procedura standard tramite Google Play Store.
Indice generale. Il BACK-END...3 COME CONFIGURARE JOOMLA...4 Sito...4 Locale...5 Contenuti...5
Guida a Joomla Indice generale Il BACK-END...3 COME CONFIGURARE JOOMLA...4 Sito...4 Locale...5 Contenuti...5 Il BACK-END La gestione di un sito Joomla ha luogo attraverso il pannello di amministrazione
FIRESHOP.NET. Gestione Lotti & Matricole. www.firesoft.it
FIRESHOP.NET Gestione Lotti & Matricole www.firesoft.it Sommario SOMMARIO Introduzione... 3 Configurazione... 6 Personalizzare le etichette del modulo lotti... 6 Personalizzare i campi che identificano
LUdeS Informatica 2 EXCEL. Seconda parte AA 2013/2014
LUdeS Informatica 2 EXCEL Seconda parte AA 2013/2014 STAMPA Quando si esegue il comando FILE STAMPA, Excel manda alla stampante tutte le celle del foglio di lavoro corrente che hanno un contenuto. Il numero
Excel. A cura di Luigi Labonia. e-mail: [email protected]
Excel A cura di Luigi Labonia e-mail: [email protected] Introduzione Un foglio elettronico è un applicazione comunemente usata per bilanci, previsioni ed altri compiti tipici del campo amministrativo
Sistema Informativo Gestione Fidelizzazione Clienti MANUALE D USO
Sistema Informativo Gestione Fidelizzazione Clienti MANUALE D USO Login All apertura il programma controlla che sia stata effettuata la registrazione e in caso negativo viene visualizzato un messaggio.
Manuale NetSupport v.10.70.6 Liceo G. Cotta Marco Bolzon
NOTE PRELIMINARI: 1. La versione analizzata è quella del laboratorio beta della sede S. Davide di Porto, ma il programma è presente anche nel laboratorio alfa (Porto) e nel laboratorio di informatica della
Configuration Managment Configurare EC2 su AWS. Tutorial. Configuration Managment. Configurare il servizio EC2 su AWS. Pagina 1
Tutorial Configuration Managment Configurare il servizio EC2 su AWS Pagina 1 Sommario 1. INTRODUZIONE... 3 2. PROGRAMMI NECESSARI... 4 3. PANNELLO DI CONTROLLO... 5 4. CONFIGURARE E LANCIARE UN ISTANZA...
CORSO ACCESS PARTE II. Esistono diversi tipi di aiuto forniti con Access, generalmente accessibili tramite la barra dei menu (?)
Ambiente Access La Guida di Access Esistono diversi tipi di aiuto forniti con Access, generalmente accessibili tramite la barra dei menu (?) Guida in linea Guida rapida Assistente di Office indicazioni
CTVClient. Dopo aver inserito correttamente i dati, verrà visualizzata la schermata del tabellone con i giorni e le ore.
CTVClient Il CTVClient è un programma per sistemi operativi Windows che permette la consultazione e la prenotazione delle ore dal tabellone elettronico del Circolo Tennis Valbisenzio. Per utilizzarlo è
Introduzione. Installare EMAS Logo Generator
EMAS Logo Generator Indice Introduzione... 3 Installare EMAS Logo Generator... 3 Disinstallare EMAS Logo Generator... 4 Schermata iniziale... 5 Creare il Logo... 7 Impostazioni... 7 Colore...8 Lingua del
Direzione Centrale per le Politiche dell Immigrazione e dell Asilo
Direzione Centrale per le Politiche dell Immigrazione e dell Asilo Sistema inoltro telematico domande di nulla osta, ricongiungimento e conversioni Manuale utente Versione 2 Data creazione 02/11/2007 12.14.00
PRODUZIONE PAGELLE IN FORMATO PDF
Requisiti minimi: PRODUZIONE, FIRMA E PUBBLICAZIONE DELLA PAGELLA ELETTRONICA CON ALUNNI WINDOWS PRODUZIONE PAGELLE IN FORMATO PDF Argo Alunni Windows aggiornato alla versione più recente. Adobe PDF CREATOR,
Guida alla configurazione della posta elettronica dell Ateneo di Ferrara sui più comuni programmi di posta
Guida alla configurazione della posta elettronica dell Ateneo di Ferrara sui più comuni programmi di posta. Configurazione Account di posta dell Università di Ferrara con il Eudora email Eudora email può
Manuale per la configurazione di AziendaSoft in rete
Manuale per la configurazione di AziendaSoft in rete Data del manuale: 7/5/2013 Aggiornamento del manuale: 2.0 del 10/2/2014 Immagini tratte da Windows 7 Versione di AziendaSoft 7 Sommario 1. Premessa...
Procedura SMS. Manuale Utente
Procedura SMS Manuale Utente INDICE: 1 ACCESSO... 4 1.1 Messaggio di benvenuto... 4 2 UTENTI...4 2.1 Gestione utenti (utente di Livello 2)... 4 2.1.1 Creazione nuovo utente... 4 2.1.2 Modifica dati utente...
Configurazione della ricerca desktop di Nepomuk. Sebastian Trüg Anne-Marie Mahfouf Traduzione della documentazione in italiano: Federico Zenith
Configurazione della ricerca desktop di Nepomuk Sebastian Trüg Anne-Marie Mahfouf Traduzione della documentazione in italiano: Federico Zenith 2 Indice 1 Introduzione 4 1.1 Impostazioni di base....................................
12 - Introduzione alla Programmazione Orientata agli Oggetti (Object Oriented Programming OOP)
12 - Introduzione alla Programmazione Orientata agli Oggetti (Object Oriented Programming OOP) Programmazione e analisi di dati Modulo A: Programmazione in Java Paolo Milazzo Dipartimento di Informatica,
Figura 1 Le Icone dei file di Excel con e senza macro.
18 Le macro Le macro rappresentano una soluzione interessante per automatizzare e velocizzare l esecuzione di operazioni ripetitive. Le macro, di fatto, sono porzioni di codice VBA (Visual Basic for Applications)
Scrivere uno script php che, dato un array associativo PERSONE le cui chiavi sono i
Esercizi PHP 1. Scrivere uno script PHP che produca in output: 1. La tabellina del 5 2. La tavola Pitagorica contenuta in una tabella 3. La tabellina di un numero ricevuto in input tramite un modulo. Lo
FIRESHOP.NET. Gestione del taglia e colore. www.firesoft.it
FIRESHOP.NET Gestione del taglia e colore www.firesoft.it Sommario SOMMARIO Introduzione... 3 Configurazione iniziale... 5 Gestione delle varianti... 6 Raggruppamento delle varianti... 8 Gestire le varianti
1. RETI INFORMATICHE CORSO DI LAUREA IN INGEGNERIA INFORMATICA SPECIFICHE DI PROGETTO A.A. 2013/2014. 1.1 Lato client
RETI INFORMATICHE - SPECIFICHE DI PROGETTO A.A. 2013/2014 1. RETI INFORMATICHE CORSO DI LAUREA IN INGEGNERIA INFORMATICA SPECIFICHE DI PROGETTO A.A. 2013/2014 Il progetto consiste nello sviluppo di un
E possibile modificare la lingua dei testi dell interfaccia utente, se in inglese o in italiano, dal menu [Tools
Una breve introduzione operativa a STGraph Luca Mari, versione 5.3.11 STGraph è un sistema software per creare, modificare ed eseguire modelli di sistemi dinamici descritti secondo l approccio agli stati
Visual basic base Lezione 01. L'ambiente di sviluppo
L'ambiente di sviluppo L'ambiente di sviluppo Visual basic è un linguaggio di programmazione Microsoft. In questo corso prenderemo in considerazione, l'ultima versione. net di questo linguaggio. Microsoft
Guida informatica per l associazione #IDEA
Guida informatica per l associazione #IDEA Questa guida vi spiegherà come utilizzare al meglio gli strumenti informatici che utilizza l associazione #IDEA in modo da facilitare il coordinamento con tutti
Corso basi di dati Installazione e gestione di PWS
Corso basi di dati Installazione e gestione di PWS Gianluca Di Tomassi Email: [email protected] Università di Roma Tre Cosa è PWS? Il Personal Web Server altro non è che una versione ridotta del
Come modificare la propria Home Page e gli elementi correlati
Come modificare la propria Home Page e gli elementi correlati Versione del documento: 3.0 Ultimo aggiornamento: 2006-09-15 Riferimento: webmaster ([email protected]) La modifica delle informazioni
MANUALE UTENTE Fiscali Free
MANUALE UTENTE Fiscali Free Le informazioni contenute in questa pubblicazione sono soggette a modifiche da parte della ComputerNetRimini. Il software descritto in questa pubblicazione viene rilasciato
Gestione Rapporti (Calcolo Aree)
Gestione Rapporti (Calcolo Aree) L interfaccia dello strumento generale «Gestione Rapporti»...3 Accedere all interfaccia (toolbar)...3 Comandi associati alle icone della toolbar...4 La finestra di dialogo
CRM Configurazione e gestione accessi
Gestione dei Reparti VtigerCrm fornisce funzionalità per configurare i privilegi di accesso ai dati in maniera granulare per ogni utente o gruppo di utenti registrato nel programma. Le funzionalità di
Visual Basic.NET La Gestione degli Errori di Federico BARBATI
Generalità Visual Basic.NET La Gestione degli Errori di Federico BARBATI La gestione degli errori, è una parte fondamentale di un codice ben progettato. Fino ad oggi, gli errori nelle applicazioni scritte
Domande e Risposte ALLEGATI CLIENTI E FORNITORI. DATALOG Soluzioni Integrate
KING Domande e Risposte ALLEGATI CLIENTI E FORNITORI DATALOG Soluzioni Integrate - 2 - Domande e Risposte Allegati Clienti e Fornitori Sommario Premessa.... 3 Introduzione... 4 Elenco delle domande...
Manuale Utente MyFastPage
Manuale MyFastPage Utente Elenco dei contenuti 1. Cosa è MyVoice Home?... 4 1.1. Introduzione... 5 2. Utilizzo del servizio... 6 2.1. Accesso... 6 2.2. Disconnessione... 7 2.3. Configurazione base Profilo
