Symfony - Conceptos avanzados

En este capítulo, aprenderemos sobre algunos conceptos avanzados en el marco Symfony.

Caché HTTP

El almacenamiento en caché en una aplicación web mejora el rendimiento. Por ejemplo, los productos calientes en una aplicación web de carrito de compras pueden almacenarse en caché durante un tiempo limitado, de modo que se puedan presentar al cliente de manera rápida sin llegar a la base de datos. A continuación se muestran algunos componentes básicos de Cache.

Elemento de caché

El elemento de caché es una única unidad de información almacenada como un par clave / valor. loskey debe ser una cuerda y valuepuede ser cualquier objeto PHP. Los objetos PHP se almacenan como cadenas por serialización y se vuelven a convertir en objetos mientras se leen los elementos.

Adaptador de caché

El adaptador de caché es el mecanismo real para almacenar el artículo en una tienda. La tienda puede ser una memoria, sistema de archivos, base de datos, redis, etc. El componente de caché proporciona unAdapterInterfacea través del cual un adaptador puede almacenar un elemento de caché en una tienda de back-end. Hay muchos adaptadores de caché integrados disponibles. Algunos de ellos son los siguientes:

  • Adaptador de caché de matriz: los elementos de caché se almacenan en una matriz de PHP.

  • Adaptador de caché del sistema de archivos: los elementos de la caché se almacenan en archivos.

  • Adaptador de caché de archivos PHP: los elementos de la caché se almacenan como archivos php.

  • Adaptador de caché de APCu: los elementos de caché se almacenan en la memoria compartida mediante la extensión PHP APCu.

  • Adaptador de caché de Redis: los elementos de caché se almacenan en el servidor de Redis.

  • Adaptador de caché PDO y Doctrine DBAL: los elementos de caché se almacenan en la base de datos.

  • Adaptador de caché en cadena: combina varios adaptadores de caché con fines de replicación.

  • Adaptador de caché de proxy: los elementos de caché se almacenan mediante un adaptador de terceros, que implementa CacheItemPoolInterface.

Grupo de caché

Cache Pool es un repositorio lógico de elementos de caché. Los grupos de caché se implementan mediante adaptadores de caché.

Aplicación sencilla

Creemos una aplicación sencilla para comprender el concepto de caché.

Step 1 - Crea una nueva aplicación, cache-example.

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

Step 2 - Instalar componente de caché.

composer require symfony/cache

Step 3 - Cree un adaptador de sistema de archivos.

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

Step 4 - Crea un elemento de caché usando getItem y setmétodo de adaptador. getItem recupera el elemento de caché usando su clave. si la clave no es constante, crea un nuevo elemento. El método set almacena los datos reales.

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

Step 5 - Acceda al elemento de caché usando getItem, isHit y getmétodo. isHit informa la disponibilidad del elemento de caché y el método get proporciona los datos reales.

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

Step 6 - Eliminar el elemento de la caché usando deleteItem método.

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

La lista de códigos completa es la siguiente.

<?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');  
?>

Resultado

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

Depurar

La depuración es una de las actividades más frecuentes al desarrollar una aplicación. Symfony proporciona un componente independiente para facilitar el proceso de depuración. Podemos habilitar las herramientas de depuración de Symfony simplemente llamando alenable método de la clase Debug.

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

Symfony ofrece dos clases, ErrorHandler y ExceptionHandlercon fines de depuración. Mientras ErrorHandler detecta los errores de PHP y los convierte en excepciones, ErrorException o FatalErrorException, ExceptionHandler detecta las excepciones de PHP no detectadas y las convierte en una respuesta PHP útil. ErrorHandler y ExceptionHandler están deshabilitados de forma predeterminada. Podemos habilitarlo usando el método de registro.

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

En una aplicación web Symfony, el debug environmentes proporcionado por DebugBundle. Registre el paquete en AppKernel'sregisterBundles método para habilitarlo.

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

Perfilador

El desarrollo de una aplicación necesita una herramienta de creación de perfiles de clase mundial. La herramienta de creación de perfiles recopila toda la información en tiempo de ejecución sobre una aplicación, como el tiempo de ejecución, el tiempo de ejecución de los módulos individuales, el tiempo que tarda una actividad en la base de datos, el uso de la memoria, etc. Una aplicación web necesita mucha más información, como el tiempo de la solicitud, tiempo necesario para crear una respuesta, etc. además de las métricas anteriores.

Symfony habilita toda esa información en una aplicación web de forma predeterminada. Symfony proporciona un paquete separado para la creación de perfiles web llamadoWebProfilerBundle. El paquete de generador de perfiles web se puede habilitar en una aplicación web registrando el paquete en el método registerBundles de AppKernel.

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

El componente de perfil web se puede configurar en web_profile section del archivo de configuración de la aplicación, app/config/config.xml

web_profiler: 
   toolbar:      false 
   position:     bottom

La aplicación Symfony muestra los datos perfilados en la parte inferior de la página como una sección distinta.

Symfony también proporciona una manera fácil de agregar detalles personalizados sobre la página en los datos del perfil usando DataCollectorInterface interfacey plantilla de ramita. En resumen, Symfony permite a un desarrollador web crear una aplicación de clase mundial al proporcionar un excelente marco de creación de perfiles con relativa facilidad.

Seguridad

Como se mencionó anteriormente, Symfony proporciona un marco de seguridad robusto a través de su componente de seguridad. El componente de seguridad se divide en cuatro subcomponentes de la siguiente manera.

  • symfony / security-core: funcionalidad de seguridad básica.
  • symfony / security-http: función de seguridad integrada en el protocolo HTTP.
  • symfony / security-csrf: protección contra la falsificación de solicitudes entre sitios en una aplicación web.
  • symfony / security-acl: marco de seguridad avanzado basado en listas de control de acceso.

Autenticación y autorización simples

Aprendamos el concepto de autenticación y autorización utilizando una sencilla aplicación de demostración.

Step 1 - Crea una nueva aplicación web securitydemo usando el siguiente comando.

symfony new securitydemo

Step 2- Habilite la función de seguridad en la aplicación utilizando el archivo de configuración de seguridad. La configuración relacionada con la seguridad se coloca en un archivo separado,security.yml. La configuración predeterminada es la siguiente.

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

La configuración predeterminada habilita el proveedor de seguridad basado en memoria y el acceso anónimo a todas las páginas. La sección de firewall excluye los archivos que coinciden con el patrón,^/(_(profiler|wdt)|css|images|js)/desde el marco de seguridad. El patrón predeterminado incluye hojas de estilo, imágenes y JavaScripts (además de herramientas de desarrollo como Profiler).

Step 3 - Habilite el sistema de autenticación de seguridad basado en HTTP agregando la opción http_basic en la sección principal de la siguiente manera.

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

Step 4- Agregue algunos usuarios en la sección de proveedor de memoria. Además, agregue roles para los usuarios.

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

Hemos agregado dos usuarios, el usuario en el rol ROLE_USER y el administrador en el rol ROLE_ADMIN.

Step 5- Agregue el codificador para obtener detalles completos del usuario que ha iniciado sesión actualmente. El propósito del codificador es obtener detalles completos del objeto de usuario actual de la solicitud web.

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

Symfony proporciona una interfaz, UserInterface para obtener detalles del usuario como nombre de usuario, roles, contraseña, etc. Necesitamos implementar la interfaz según nuestro requisito y configurarla en la sección del codificador.

Por ejemplo, consideremos que los datos del usuario están en la base de datos. Luego, necesitamos crear una nueva clase de usuario e implementar métodos de UserInterface para obtener los detalles del usuario de la base de datos. Una vez que los datos están disponibles, el sistema de seguridad los usa para permitir / denegar al usuario. Symfony proporciona una implementación de usuario predeterminada para el proveedor de memoria. El algoritmo se utiliza para descifrar la contraseña del usuario.

Step 6 - Cifre la contraseña del usuario usando bcryptalgoritmo y colóquelo en el archivo de configuración. Desde que usamosbcryptalgoritmo, el objeto Usuario intenta descifrar la contraseña especificada en el archivo de configuración y luego intenta coincidir con la contraseña ingresada por el usuario. La aplicación de consola Symfony proporciona un comando simple para cifrar la contraseña.

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 - Utilice el comando para generar la contraseña cifrada y actualizarla en el archivo de configuración.

# 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- Ahora, aplique la seguridad a alguna sección de la aplicación. Por ejemplo, restrinja la sección de administración a los usuarios en función, ROLE_ADMIN.

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

Step 9 - Agregue una página de administración en DefaultController de la siguiente manera.

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

Step 10- Finalmente, acceda a la página de administración para comprobar la configuración de seguridad en un navegador. El navegador le pedirá el nombre de usuario y la contraseña y solo permitirá usuarios configurados.

Resultado

Flujo de trabajo

El flujo de trabajo es un concepto avanzado que se utiliza en muchas aplicaciones empresariales. En una aplicación de comercio electrónico, el proceso de entrega del producto es un flujo de trabajo. El producto se factura primero (creación del pedido), se adquiere en la tienda y se empaqueta (empaquetado / listo para enviar) y se envía al usuario. Si hay algún problema, el producto regresa del usuario y el pedido se revierte. El orden del flujo de acción es muy importante. Por ejemplo, no podemos entregar un producto sin facturación.

El componente Symfony proporciona una forma orientada a objetos para definir y administrar un flujo de trabajo. Cada paso de un proceso se llamaplace y la acción necesaria para moverse de un lugar a otro se llama transition. La colección de lugares y la transición para crear un flujo de trabajo se denominaWorkflow definition.

Comprendamos el concepto de flujo de trabajo creando una aplicación simple para la gestión de licencias.

Step 1 - Crea una nueva aplicación, workflow-example.

cd /path/to/dev 
mkdir workflow-example 

cd workflow-example 
composer require symfony/workflow

Step 2 - Crea una nueva clase, Leave teniendo applied_by, leave_on y status atributos.

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

Aquí, application_by se refiere a los empleados que quieren irse. Leave_on se refiere a la fecha de la licencia. estado se refiere al estado de licencia.

Step 3 - La gestión de licencias tiene cuatro lugares, aplicada, en proceso y aprobada / rechazada.

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']);

Aquí, hemos creado una nueva definición usando DefinitionBuilder y lugares agregados usando addPlaces método.

Step 4 - Definir las acciones necesarias para desplazarse de un lugar a otro.

$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'));

Aquí, tenemos tres transiciones, to_process, approve y reject. to_process transition acepta la solicitud de licencia y mueve el lugar de aplicado a in_process. aprobar transición aprueba la solicitud de licencia y mueve el lugar a aprobado. De manera similar, la transición de rechazo rechaza la solicitud de licencia y mueve el lugar a rechazado. Hemos creado todas las transiciones utilizando el método addTransition.

Step 5 - Construya la definición usando el método de construcción.

$definition = $builder->build();

Step 6 - Opcionalmente, la definición se puede volcar como formato de punto gráfico, que se puede convertir a un archivo de imagen con fines de referencia.

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

Step 7 - Cree una tienda de marcado, que se utiliza para almacenar los lugares / estado actuales del objeto.

$marking = new SingleStateMarkingStore('status');

Aquí, hemos utilizado SingleStateMarkingStoreclass para crear la marca y marca el estado actual en la propiedad de estado del objeto. En nuestro ejemplo, el objeto es Dejar objeto.

Step 8 - Cree el flujo de trabajo utilizando definición y marcado.

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

Aquí, hemos utilizado Workflow class para crear el flujo de trabajo.

Step 9 - Agregue el flujo de trabajo al registro del marco de flujo de trabajo usando Registry clase.

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

Step 10 - Finalmente, use el flujo de trabajo para encontrar si una determinada transición se aplica usando can método y si es así, applyla transición usando el método de aplicación. Cuando se aplica una transición, el estado del objeto se mueve de un lugar a otro.

$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 codificación completa es la siguiente:

<?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";  
?>

Resultado

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