Un'applicazione moderna è composta da migliaia di oggetti attraverso i quali vengono implementate le singole funzionalità dell'applicazione stessa. Uno svantaggio di questo approccio è la necessità di dover organizzare e istanziare i singoli oggetti all'occorrenza.
Una delle caratteristiche più importanti di Symfony Framework 2 è il Service Container ovvero uno strumento attraverso il quale è possibile istanziare, organizzare e recuperare i singoli oggetti che compongono l'applicazione.
In Symfony tali oggetti sono chiamati servizi, da qui il nome di Service Container: un contenitore che consente di standardizzare e centralizzare i servizi. Tutte le classi del core di Symfony sono contenute all'interno del Service Container quindi lo strumento è essenziale per sfruttare in pieno il framework.
Creare un servizio
Supponiamo di voler creare un semplice servizio che, data una stringa, verifichi se essa sia o meno una password valida. Utilizzeremo una espressione regolare per verificare che sia maggiore di length
caratteri e che contenga almeno un carattere maiuscolo e un numero al suo interno.
Di seguito verrà proposto il codice della classe contenuta in Service/PasswordValidator.php
:
<?php
namespace Acme\DemoBundle\Service;
class PasswordValidator
{
private $pattern;
public function __construct($length) {
$this->pattern = '/\A(?=[-_a-zA-Z0-9]*?[A-Z])(?=[-_a-zA-Z0-9]*?[a-z])(?=[-_a-zA-Z0-9]*?[0-9])[-_a-zA-Z0-9]{'.$length.',}\z/';
}
public function validate($password) {
preg_match($this->pattern, $password, $matches);
return !empty($matches);
}
}
Un utilizzo classico della classe potrebbe essere quello riportato di seguito:
$pvalidator = new PasswordValidator(6);
var_dump( $pvalidator->validate('ciaomamma') );
var_dump( $pvalidator->validate('ciaomamma1') );
var_dump( $pvalidator->validate('ciaomamma1A') );
Attraverso il Service Container, invece, senza la necessità di istanziare la classe nel nostro controller, possiamo richiamarla come in questo esempio:
$this->get('password_validator')->validate('ciaomamma1A');
Se inseriamo all'interno di un nostro controller la stringa appena proposta, ci verrà restituito il seguente errore:
You have requested a non-existent service "password_validator".
Symfony, quindi, ci avvisa che stiamo richiamando, attraverso il metodo get()
, con cui appunto accediamo al service container, un servizio che ancora non è stato registrato. Vediamo quindi come rendere noto a Symfony dell'esistenza del servizio.
All'interno del file src/Acme/DemoBundle/Resources/config/services.yml
abbiamo già dei servizi registrati. Dobbiamo aggiungere qui il nostro PasswordValidator.
In alcune configurazioni, potrebbe essere presente il file services.xml
anziché il file YAML. Il funzionamento è lo stesso, ciò che cambia è la sola sintassi.
Aggiungiamo quindi all'interno del tag <services>
il seguente codice:
services:
password_validator:
class: Acme\DemoBundle\Service\PasswordValidator
Se proviamo a ricaricare la pagina ora, avremo un nuovo errore ma diverso dal primo:
Warning: Missing argument 1 for Acme\DemoBundle\Service\PasswordValidator::__construct()
Questo perché nel costruttore della classe abbiamo messo come obbligatorio il primo parametro, ovvero la lunghezza minima della stringa. Dato che il Service Container si occupa anche di instanziare l'oggetto, in questo caso esso ci avvisa che non riesce ad effettuarlo correttamente in quanto non conosce il valore del parametro length
. Per fare ciò, possiamo "iniettare" la lunghezza della stringa direttamente nel file di configurazione:
services:
password_validator:
class: Acme\DemoBundle\Service\PasswordValidator
arguments:
- %password_validator.length%
parameters:
password_validator.length: 6
A questo punto ricaricando la pagina non avremo più errori e potremo utilizzare il nostro servizio.
Iniettare Servizi
Nell'esempio precedente abbiamo visto che possiamo iniettare parametri ad un servizio. Questi parametri, però, non sempre sono tipi semplici come array, stringhe o interi. Spesso sono altri servizi da cui dipende l'oggetto su cui stiamo lavorando. Vediamo quindi come iniettare un servizio all'interno di un altro servizio (da qui Dependency Injection).
Supponiamo di voler creare un nuovo servizio chiamato PasswordGenerator
che si occupa di generare una nuova password e che quest'ultimo necessiti del precedente PasswordValidator
per verificare che la password generata sia corretta. Vediamo quindi come implementare la dipendenza.
Innanzitutto creiamo e registriamo il nuovo servizio:
<?php
namespace Acme\DemoBundle\Service;
class PasswordGenerator
{
public function __construct($length, PasswordValidator $password_validator) {
$this->password_validator = $password_validator;
$this->length = $length;
}
public function generate() {
$password = $this->_generatePassword();
if( $this->password_validator->validate($password) ) {
return $password;
} else {
return "";
}
}
protected function _generatePassword() {
return "Questa_e_una_password_fittizia_1";
}
}
Ecco invece il codice del relativo file services.yml
:
services:
password_validator:
class: Acme\DemoBundle\Service\PasswordValidator
arguments:
- %password_validator.length%
password_generator:
class: Acme\DemoBundle\Service\PasswordGenerator
arguments:
- %password_validator.length%
- @password_validator
parameters:
password_validator.length: 6
Come possiamo notare, l'unica differenza rispetto a quanto visto è che nel file di configurazione abbiamo richiamato come secondo parametro il servizio password_validator
definito in precedenza, anteponendo la @
. È questa infatti la modalità con cui vengono iniettati i servizi.
In questa lezione abbiamo analizzato una delle componenti più importanti di Symfony Framework 2. Nella prossima lezione ci occuperemo invece delle traduzioni, affrontando i discorso della creazione di applicazioni multilingua.