Symfony - Concetti avanzati

In questo capitolo, impareremo alcuni concetti avanzati nel framework Symfony.

Cache HTTP

La memorizzazione nella cache in un'applicazione Web migliora le prestazioni. Ad esempio, i prodotti caldi in un'applicazione web del carrello degli acquisti possono essere memorizzati nella cache per un periodo di tempo limitato, in modo che possano essere presentati al cliente in modo rapido senza toccare il database. Di seguito sono riportati alcuni componenti di base di Cache.

Elemento cache

Cache Item è una singola unità di informazioni memorizzata come coppia chiave / valore. Ilkey dovrebbe essere stringa e valuepuò essere qualsiasi oggetto PHP. Gli oggetti PHP vengono memorizzati come stringa dalla serializzazione e riconvertiti in oggetti durante la lettura degli elementi.

Adattatore cache

Cache Adapter è il meccanismo effettivo per archiviare l'articolo in un negozio. L'archivio può essere una memoria, un file system, un database, un redis, ecc. Il componente cache fornisce un fileAdapterInterfaceattraverso il quale un adattatore può memorizzare l'elemento della cache in un archivio back-end. Sono disponibili molti adattatori cache incorporati. Alcuni di loro sono i seguenti:

  • Adattatore cache di array: gli elementi della cache vengono archiviati nell'array PHP.

  • Adattatore per la cache del file system: gli elementi della cache vengono archiviati nei file.

  • Adattatore cache file PHP: gli elementi della cache vengono archiviati come file php.

  • Adattatore cache APCu: gli elementi della cache vengono archiviati nella memoria condivisa utilizzando l'estensione PHP APCu.

  • Redis Cache Adapter: gli elementi della cache vengono archiviati nel server Redis.

  • PDO e Doctrine DBAL Cache Adapter - Gli elementi della cache vengono memorizzati nel database.

  • Chain Cache Adapter: combina più adattatori cache a scopo di replica.

  • Adattatore cache proxy: gli elementi della cache vengono archiviati utilizzando un adattatore di terze parti, che implementa CacheItemPoolInterface.

Pool di cache

Cache Pool è un repository logico di elementi della cache. I pool di cache vengono implementati dagli adattatori della cache.

Applicazione semplice

Creiamo una semplice applicazione per comprendere il concetto di cache.

Step 1 - Crea una nuova applicazione, cache-example.

cd /path/to/app 
mkdir cache-example 
cd cache-example

Step 2 - Installa il componente cache.

composer require symfony/cache

Step 3 - Crea un adattatore per file system.

require __DIR__ . '/vendor/autoload.php';  
use Symfony\Component\Cache\Adapter\FilesystemAdapter;  
$cache = new FilesystemAdapter();

Step 4 - Crea un elemento della cache utilizzando getItem e setmetodo di adattatore. getItem recupera l'elemento della cache utilizzando la sua chiave. se la chiave non è persistente, crea un nuovo elemento. Il metodo set memorizza i dati effettivi.

$usercache = $cache->getitem('item.users'); 
$usercache->set(['jon', 'peter']); 
$cache->save($usercache);

Step 5 - Accedi all'elemento della cache utilizzando getItem, isHit e getmetodo. isHit informa la disponibilità dell'elemento cache e il metodo get fornisce i dati effettivi.

$userCache = $cache->getItem('item.users'); 
if(!$userCache->isHit()) { 
   echo "item.users is not available"; 
} else { 
   $users = $userCache->get(); 
   var_dump($users); 
}

Step 6 - Elimina l'elemento della cache utilizzando deleteItem metodo.

$cache->deleteItem('item.users');

L'elenco completo del codice è il seguente.

<?php  
   require __DIR__ . '/vendor/autoload.php'; 
   use Symfony\Component\Cache\Adapter\FilesystemAdapter;  

   $cache = new FilesystemAdapter();  
   $usercache = $cache->getitem('item.users'); 
   $usercache->set(['jon', 'peter']); 
   $cache->save($usercache);  
   $userCache = $cache->getItem('item.users'); 
   
   if(!$userCache->isHit()) { 
      echo "item.users is not available"; 
   } else { 
      $users = $userCache->get(); 
      var_dump($users); 
   }  
   $cache->deleteItem('item.users');  
?>

Risultato

array(2) { 
   [0]=> 
   string(3) "jon" 
   [1]=> 
   string(5) "peter" 
}

Debug

Il debug è una delle attività più frequenti durante lo sviluppo di un'applicazione. Symfony fornisce un componente separato per facilitare il processo di debugging. Possiamo abilitare gli strumenti di debug di Symfony semplicemente chiamando il fileenable metodo della classe Debug.

use Symfony\Component\Debug\Debug  
Debug::enable()

Symfony fornisce due classi, ErrorHandler e ExceptionHandlera scopo di debug. Mentre ErrorHandler rileva gli errori PHP e li converte in eccezioni, ErrorException o FatalErrorException, ExceptionHandler rileva le eccezioni PHP non rilevate e le converte in utili risposte PHP. ErrorHandler e ExceptionHandler sono disabilitati per impostazione predefinita. Possiamo abilitarlo utilizzando il metodo di registrazione.

use Symfony\Component\Debug\ErrorHandler; 
use Symfony\Component\Debug\ExceptionHandler;  
ErrorHandler::register(); 
ExceptionHandler::register();

In un'applicazione web Symfony, il debug environmentè fornito da DebugBundle. Registra il pacchetto in AppKernelregisterBundles metodo per abilitarlo.

if (in_array($this->getEnvironment(), ['dev', 'test'], true)) { 
   $bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle(); 
}

Profiler

Lo sviluppo di un'applicazione richiede uno strumento di profilazione di livello mondiale. Lo strumento di profilazione raccoglie tutte le informazioni di runtime su un'applicazione come il tempo di esecuzione, il tempo di esecuzione dei singoli moduli, il tempo impiegato da un'attività del database, l'utilizzo della memoria, ecc. Un'applicazione web necessita di molte più informazioni come l'ora della richiesta, tempo impiegato per creare una risposta, ecc. oltre alle metriche di cui sopra.

Symfony abilita tutte queste informazioni in un'applicazione web per impostazione predefinita. Symfony fornisce un bundle separato per la profilazione web chiamatoWebProfilerBundle. Il bundle del profiler Web può essere abilitato in un'applicazione Web registrando il bundle nel metodo registerBundles di AppKernel.

if (in_array($this->getEnvironment(), ['dev', 'test'], true)) { 
   $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); 
}

Il componente del profilo web può essere configurato in web_profile section del file di configurazione dell'applicazione, app/config/config.xml

web_profiler: 
   toolbar:      false 
   position:     bottom

L'applicazione Symfony mostra i dati profilati in fondo alla pagina come una sezione distinta.

Symfony fornisce anche un modo semplice per aggiungere dettagli personalizzati sulla pagina nei dati del profilo usando DataCollectorInterface interfacee modello di ramoscello. In breve, Symfony consente a uno sviluppatore web di creare un'applicazione di livello mondiale fornendo un ottimo framework di profiling con relativa facilità.

Sicurezza

Come discusso in precedenza, Symfony fornisce un solido framework di sicurezza attraverso il suo componente di sicurezza. La componente sicurezza è suddivisa in quattro sottocomponenti come segue.

  • symfony / security-core - Funzionalità di sicurezza principali.
  • symfony / security-http - Funzionalità di sicurezza integrata nel protocollo HTTP.
  • symfony / security-csrf - Protezione contro la falsificazione di richieste cross-site in un'applicazione web.
  • symfony / security-acl - Framework di sicurezza basato su elenchi di controllo di accesso avanzato.

Autenticazione e autorizzazione semplici

Impariamo il concetto di autenticazione e autorizzazione utilizzando una semplice applicazione demo.

Step 1 - Crea una nuova applicazione web securitydemo utilizzando il seguente comando.

symfony new securitydemo

Step 2- Abilitare la funzione di sicurezza nell'applicazione utilizzando il file di configurazione della sicurezza. La configurazione relativa alla sicurezza viene inserita in un file separato,security.yml. La configurazione predefinita è la seguente.

security: 
   providers: 
      in_memory: 
         memory: ~ 
   firewalls: 
      dev: 
         pattern: ^/(_(profiler|wdt)|css|images|js)/ 
         security: false  
   main: 
      anonymous: ~ 
      #http_basic: ~ 
      #form_login: ~

La configurazione predefinita abilita il provider di sicurezza basato sulla memoria e l'accesso anonimo a tutte le pagine. La sezione firewall esclude i file che corrispondono al pattern,^/(_(profiler|wdt)|css|images|js)/dal framework di sicurezza. Il modello predefinito include fogli di stile, immagini e JavaScript (oltre a strumenti di sviluppo come il profiler).

Step 3 - Abilita il sistema di autenticazione della sicurezza basato su HTTP aggiungendo l'opzione http_basic nella sezione principale come segue.

security: 
   # ...  
   firewalls: 
      # ...  
      main: 
         anonymous: ~ 
         http_basic: ~ 
         #form_login: ~

Step 4- Aggiungi alcuni utenti nella sezione del provider di memoria. Inoltre, aggiungi ruoli per gli utenti.

security: 
   providers: 
      in_memory: 
         memory: 
            users: 
               myuser: 
                  password: user 
                  roles: 'ROLE_USER' 
                     myadmin: 
                        password: admin 
                        roles: 'ROLE_ADMIN'

Abbiamo aggiunto due utenti, utente nel ruolo ROLE_USER e admin nel ruolo ROLE_ADMIN.

Step 5- Aggiungi l'encoder per ottenere i dettagli completi dell'utente attualmente connesso. Lo scopo del codificatore è ottenere i dettagli completi dell'oggetto utente corrente dalla richiesta web.

security: 
   # ... 
   encoders: 
      Symfony\Component\Security\Core\User\User: bcrypt 
      # ...

Symfony fornisce un'interfaccia, UserInterface per ottenere i dettagli dell'utente come nome utente, ruoli, password, ecc. Dobbiamo implementare l'interfaccia secondo i nostri requisiti e configurarla nella sezione codificatore.

Ad esempio, si consideri che i dettagli dell'utente sono nel database. Quindi, dobbiamo creare una nuova classe User e implementare i metodi UserInterface per ottenere i dettagli dell'utente dal database. Una volta che i dati sono disponibili, il sistema di sicurezza li utilizza per consentire / negare all'utente. Symfony fornisce un'implementazione utente predefinita per il provider di memoria. L'algoritmo viene utilizzato per decrittografare la password utente.

Step 6 - Crittografa la password utente utilizzando bcryptalgoritmo e posizionarlo nel file di configurazione. Da quando abbiamo usatobcryptalgoritmo, l'oggetto Utente tenta di decrittografare la password specificata nel file di configurazione e quindi cerca di abbinarla alla password inserita dall'utente. L'applicazione console Symfony fornisce un semplice comando per crittografare la password.

php bin/console security:encode-password admin 
Symfony Password Encoder Utility 
================================  
------------------ -----------------------------------
Key   Value  
------------------ ------------------------------------
Encoder used       Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder         
Encoded password   
$2y$12$0Hy6/.MNxWdFcCRDdstHU.hT5j3Mg1tqBunMLIUYkz6..IucpaPNO    
------------------ ------------------------------------   
! [NOTE] Bcrypt encoder used: the encoder generated its own built-in salt.
[OK] Password encoding succeeded

Step 7 - Utilizzare il comando per generare la password crittografata e aggiornarla nel file di configurazione.

# To get started with security, check out the documentation: 
# http://symfony.com/doc/current/security.html 
   security:  
      # http://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded 
      providers: 
         in_memory: 
            memory: 
               users: 
                  user: 
                     password: $2y$13$WsGWNufreEnVK1InBXL2cO/U7WftvfNvH
                     Vb/IJBH6JiYoDwVN4zoi  
                     roles: 'ROLE_USER' 
                     admin: 
                        password: $2y$13$jQNdIeoNV1BKVbpnBuhKRuOL01NeMK
                        F7nEqEi/Mqlzgts0njK3toy  
                        roles: 'ROLE_ADMIN' 
                         
         encoders: 
            Symfony\Component\Security\Core\User\User: bcrypt  
         firewalls: 
            # disables authentication for assets and the profiler, 
            # adapt it according to your needs 
         dev: 
            pattern: ^/(_(profiler|wdt)|css|images|js)/
         security: false  
         main: 
            anonymous: ~ 
            # activate different ways to authenticate  
            # http://symfony.com/doc/current/security.html#a-co
            nfiguring-howyour-users-will-authenticate 
            http_basic: ~  
            # http://symfony.com/doc/current/cookbook/security/
            form_login_setup.html 
            #form_login: ~

Step 8- Ora, applica la sicurezza a qualche sezione dell'applicazione. Ad esempio, limitare la sezione di amministrazione agli utenti nel ruolo, ROLE_ADMIN.

security: 
   # ... 
      firewalls: 
         # ... 
      default: 
         # ...  
      access_control: 
         # require ROLE_ADMIN for /admin* 
         - { path: ^/admin, roles: 'ROLE_ADMIN' }

Step 9 - Aggiungi una pagina di amministrazione in DefaultController come segue.

/** 
   * @Route("/admin") 
*/ 
public function adminLandingAction() { 
   return new Response('<html><body>This is admin section.</body></html>'); 
}

Step 10- Infine, accedi alla pagina di amministrazione per verificare la configurazione della sicurezza in un browser. Il browser chiederà il nome utente e la password e consentirà solo agli utenti configurati.

Risultato

Flusso di lavoro

Il flusso di lavoro è un concetto avanzato utilizzato in molte applicazioni aziendali. In un'applicazione di e-commerce, il processo di consegna del prodotto è un flusso di lavoro. Il prodotto viene prima fatturato (creazione dell'ordine), acquistato dal negozio e imballato (imballaggio / pronto per la spedizione) e spedito all'utente. In caso di problemi, il prodotto ritorna dall'utente e l'ordine viene annullato. L'ordine del flusso d'azione è molto importante. Ad esempio, non possiamo fornire un prodotto senza fatturazione.

Il componente Symfony fornisce un modo orientato agli oggetti per definire e gestire un flusso di lavoro. Viene chiamata ogni fase di un processoplace e viene chiamata l'azione richiesta per spostarsi da un luogo all'altro transition. La raccolta di luoghi e la transizione per creare un flusso di lavoro è chiamata aWorkflow definition.

Cerchiamo di comprendere il concetto di flusso di lavoro creando una semplice applicazione per la gestione delle ferie.

Step 1 - Crea una nuova applicazione, workflow-example.

cd /path/to/dev 
mkdir workflow-example 

cd workflow-example 
composer require symfony/workflow

Step 2 - Crea una nuova classe, Leave avendo applied_by, leave_on e status attributi.

class Leave { 
   public $applied_by; 
   public $leave_on;  
   public $status; 
}

In questo caso, apply_by si riferisce ai dipendenti che vogliono ferie. leave_on si riferisce alla data del congedo. lo stato si riferisce allo stato di congedo.

Step 3 - La gestione delle ferie ha quattro posizioni, applicate, in_process e approvate / rifiutate.

use Symfony\Component\Workflow\DefinitionBuilder; 
use Symfony\Component\Workflow\Transition; 
use Symfony\Component\Workflow\Workflow; 
use Symfony\Component\Workflow\MarkingStore\SingleStateMarkingStore; 
use Symfony\Component\Workflow\Registry; 
use Symfony\Component\Workflow\Dumper\GraphvizDumper;

$builder = new DefinitionBuilder(); 
$builder->addPlaces(['applied', 'in_process', 'approved', 'rejected']);

Qui abbiamo creato una nuova definizione usando DefinitionBuilder e ha aggiunto luoghi utilizzando addPlaces metodo.

Step 4 - Definire le azioni necessarie per spostarsi da un luogo a un altro.

$builder->addTransition(new Transition('to_process', 'applied', 'in_process')); 
$builder->addTransition(new Transition('approve', 'in_process', 'approved')); 
$builder->addTransition(new Transition('reject', 'in_process', 'rejected'));

Qui abbiamo tre transizioni, to_process, approve e reject. La transizione to_process accetta l'applicazione di chiusura e sposta il punto da applicato a in_process. approva transizione approva la richiesta di ferie e sposta il luogo in approvato. Allo stesso modo, Rifiuta transizione rifiuta la domanda di permesso e sposta il luogo in rifiutato. Abbiamo creato tutte le transizioni utilizzando il metodo addTransition.

Step 5 - Costruisci la definizione utilizzando il metodo build.

$definition = $builder->build();

Step 6 - Facoltativamente, la definizione può essere scaricata come formato graphviz dot, che può essere convertito in file immagine a scopo di riferimento.

$dumper = new GraphvizDumper(); 
echo $dumper->dump($definition);

Step 7 - Creare un negozio di marcatura, che viene utilizzato per memorizzare i luoghi / lo stato corrente dell'oggetto.

$marking = new SingleStateMarkingStore('status');

Qui abbiamo usato SingleStateMarkingStoreclass per creare il segno e contrassegna lo stato corrente nella proprietà status dell'oggetto. Nel nostro esempio, l'oggetto è Lascia oggetto.

Step 8 - Creare il flusso di lavoro utilizzando la definizione e la marcatura.

$leaveWorkflow =    new Workflow($definition, $marking);

Qui abbiamo usato Workflow class per creare il flusso di lavoro.

Step 9 - Aggiungere il flusso di lavoro nel registro del framework del flusso di lavoro utilizzando Registry classe.

$registry = new Registry(); 
$registry->add($leaveWorkflow, Leave::class);

Step 10 - Infine, utilizza il flusso di lavoro per scoprire se una determinata transizione viene applicata utilizzando can metodo e, in caso affermativo, applyla transizione utilizzando il metodo apply. Quando viene applicata una transizione, lo stato dell'oggetto si sposta da un punto a un altro.

$workflow = $registry->get($leave); 
echo "Can we approve the leave now? " . $workflow->can($leave, 'approve') . "\r\n"; 
echo "Can we approve the start process now? " . $workflow->can($leave, 'to_process') . "\r\n"; 

$workflow->apply($leave, 'to_process'); 
echo "Can we approve the leave now? " . $workflow->can($leave, 'approve') . "\r\n"; 
echo $leave->status . "\r\n"; 

$workflow->apply($leave, 'approve'); 
echo $leave->status . "\r\n";

La codifica completa è la seguente:

<?php  
   require __DIR__ . '/vendor/autoload.php';  

   use Symfony\Component\Workflow\DefinitionBuilder; 
   use Symfony\Component\Workflow\Transition; 
   use Symfony\Component\Workflow\Workflow; 
   use Symfony\Component\Workflow\MarkingStore\SingleStateMarkingStore; 
   use Symfony\Component\Workflow\Registry; 
   use Symfony\Component\Workflow\Dumper\GraphvizDumper;

   class Leave { 
      public $applied_by; 
      public $leave_on;  
      public $status; 
   }  
   $builder = new DefinitionBuilder(); 
   $builder->addPlaces(['applied', 'in_process', 'approved', 'rejected']); 
   $builder->addTransition(new Transition('to_process', 'applied', 'in_process')); 
   $builder->addTransition(new Transition('approve', 'in_process', 'approved')); 
   $builder->addTransition(new Transition('reject', 'in_process', 'rejected')); 
   $definition = $builder->build();  

   // $dumper = new GraphvizDumper(); 
   // echo $dumper->dump($definition);  

   $marking = new SingleStateMarkingStore('status'); 
   $leaveWorkflow = new Workflow($definition, $marking);  
   $registry = new Registry(); 
   $registry->add($leaveWorkflow, Leave::class);  

   $leave = new Leave(); 
   $leave->applied_by = "Jon"; 
   $leave->leave_on = "1998-12-12"; 
   $leave->status = 'applied';  

   $workflow = $registry->get($leave); 
   echo "Can we approve the leave now? " . $workflow->can($leave, 'approve') . "\r\n"; 
   echo "Can we approve the start process now? " . $workflow->can($leave, 'to_process') . "\r\n"; 
   
   $workflow->apply($leave, 'to_process');  
   echo "Can we approve the leave now? " . $workflow->can($leave, 'approve') . "\r\n"; 
   echo $leave->status . "\r\n"; 
   
   $workflow->apply($leave, 'approve'); 
   echo $leave->status . "\r\n";  
?>

Risultato

Can we approve the leave now?  
Can we approve the start process now? 1 
Can we approve the leave now? 1 
in_process 
approved