Symfony - conceitos avançados

Neste capítulo, aprenderemos sobre alguns conceitos avançados no framework Symfony.

Cache HTTP

O armazenamento em cache em um aplicativo da web melhora o desempenho. Por exemplo, produtos importantes em um aplicativo da web de carrinho de compras podem ser armazenados em cache por um tempo limitado, de forma que possam ser apresentados ao cliente de maneira rápida, sem acessar o banco de dados. A seguir estão alguns componentes básicos do Cache.

Item de Cache

Item de cache é uma unidade única de informação armazenada como um par chave / valor. okey deve ser string e valuepode ser qualquer objeto PHP. Os objetos PHP são armazenados como string por serialização e convertidos de volta em objetos durante a leitura dos itens.

Adaptador de Cache

O adaptador de cache é o mecanismo real para armazenar o item em uma loja. O armazenamento pode ser uma memória, sistema de arquivos, banco de dados, redis, etc. O componente de cache fornece umAdapterInterfacepor meio do qual um adaptador pode armazenar item de cache em um armazenamento de backend. Existem muitos adaptadores de cache integrados disponíveis. Poucos deles são os seguintes -

  • Adaptador de cache de matriz - os itens de cache são armazenados na matriz de PHP.

  • Adaptador de cache do sistema de arquivos - os itens do cache são armazenados em arquivos.

  • Adaptador de cache de arquivos PHP - Os itens de cache são armazenados como arquivos php.

  • Adaptador de cache APCu - Os itens de cache são armazenados na memória compartilhada usando a extensão PHP APCu.

  • Adaptador de cache Redis - os itens de cache são armazenados no servidor Redis.

  • Adaptador de cache PDO e Doctrine DBAL - Os itens de cache são armazenados no banco de dados.

  • Adaptador de cache de cadeia - Combina vários adaptadores de cache para fins de replicação.

  • Adaptador de cache proxy - os itens de cache são armazenados usando um adaptador de terceiros, que implementa CacheItemPoolInterface.

Pool de Cache

Cache Pool é um repositório lógico de itens de cache. Os pools de cache são implementados por adaptadores de cache.

Aplicação Simples

Vamos criar um aplicativo simples para entender o conceito de cache.

Step 1 - Crie um novo aplicativo, cache-example.

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

Step 2 - Instale o componente de cache.

composer require symfony/cache

Step 3 - Crie um adaptador de sistema de arquivos.

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

Step 4 - Crie um item de cache usando getItem e setmétodo do adaptador. getItem busca o item de cache usando sua chave. se a chave não for persistente, ele cria um novo item. O método set armazena os dados reais.

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

Step 5 - Acesse o item de cache usando getItem, isHit e getmétodo. isHit informa a disponibilidade do item de cache e o método get fornece os dados reais.

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

Step 6 - Exclua o item de cache usando deleteItem método.

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

A lista de códigos completa é a seguinte.

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

A depuração é uma das atividades mais frequentes durante o desenvolvimento de um aplicativo. Symfony fornece um componente separado para facilitar o processo de depuração. Podemos habilitar as ferramentas de depuração do Symfony apenas chamando oenable método da classe Debug.

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

Symfony oferece duas classes, ErrorHandler e ExceptionHandlerpara fins de depuração. Enquanto ErrorHandler captura erros de PHP e os converte em exceções, ErrorException ou FatalErrorException, ExceptionHandler captura exceções de PHP não detectadas e as converte em respostas úteis de PHP. ErrorHandler e ExceptionHandler são desabilitados por padrão. Podemos habilitá-lo usando o método de registro.

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

Em um aplicativo da web Symfony, o debug environmenté fornecido por DebugBundle. Registre o pacote no AppKernelregisterBundles método para ativá-lo.

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

analisador

O desenvolvimento de um aplicativo precisa de uma ferramenta de criação de perfil de classe mundial. A ferramenta de criação de perfil coleta todas as informações de tempo de execução sobre um aplicativo, como tempo de execução, tempo de execução de módulos individuais, tempo gasto por uma atividade de banco de dados, uso de memória, etc. Um aplicativo da web precisa de muito mais informações, como o tempo de solicitação, tempo gasto para criar uma resposta, etc., além das métricas acima.

Symfony habilita todas essas informações em um aplicativo da web por padrão. Symfony fornece um pacote separado para perfis da web chamadoWebProfilerBundle. O pacote de criação de perfil da Web pode ser habilitado em um aplicativo da Web registrando-se o pacote no método registerBundles do AppKernel.

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

O componente de perfil da web pode ser configurado em web_profile section do arquivo de configuração do aplicativo, app/config/config.xml

web_profiler: 
   toolbar:      false 
   position:     bottom

O aplicativo Symfony mostra os dados perfilados na parte inferior da página como uma seção distinta.

Symfony também fornece uma maneira fácil de adicionar detalhes personalizados sobre a página nos dados do perfil usando DataCollectorInterface interfacee modelo de galho. Resumindo, o Symfony permite que um desenvolvedor da web crie um aplicativo de classe mundial, fornecendo uma grande estrutura de criação de perfil com relativa facilidade.

Segurança

Conforme discutido anteriormente, o Symfony fornece uma estrutura de segurança robusta por meio de seu componente de segurança. O componente de segurança é dividido em quatro subcomponentes da seguinte maneira.

  • symfony / security-core - Funcionalidade de segurança central.
  • symfony / security-http - Recurso de segurança integrado no protocolo HTTP.
  • symfony / security-csrf - Proteção contra falsificação de solicitação entre sites em uma aplicação web.
  • symfony / security-acl - Estrutura de segurança baseada em lista de controle de acesso avançado.

Autenticação e autorização simples

Vamos aprender o conceito de autenticação e autorização usando um aplicativo de demonstração simples.

Step 1 - Crie um novo aplicativo da web securitydemo usando o seguinte comando.

symfony new securitydemo

Step 2- Habilite o recurso de segurança no aplicativo usando o arquivo de configuração de segurança. A configuração relacionada à segurança é colocada em um arquivo separado,security.yml. A configuração padrão é a seguinte.

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

A configuração padrão permite provedor de segurança baseado em memória e acesso anônimo a todas as páginas. A seção de firewall exclui os arquivos que correspondem ao padrão,^/(_(profiler|wdt)|css|images|js)/da estrutura de segurança. O padrão padrão inclui folhas de estilo, imagens e JavaScripts (além de ferramentas de desenvolvimento como o criador de perfil).

Step 3 - Habilite o sistema de autenticação de segurança baseado em HTTP adicionando a opção http_basic na seção principal como segue.

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

Step 4- Adicione alguns usuários na seção de provedor de memória. Além disso, adicione funções para os usuários.

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

Adicionamos dois usuários, usuário na função ROLE_USER e administrador na função ROLE_ADMIN.

Step 5- Adicione o codificador para obter detalhes completos do usuário conectado no momento. O objetivo do codificador é obter detalhes completos do objeto de usuário atual da solicitação da web.

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

Symfony fornece uma interface, UserInterface para obter os detalhes do usuário, como nome de usuário, funções, senha, etc. Precisamos implementar a interface de acordo com nossos requisitos e configurá-la na seção do codificador.

Por exemplo, vamos considerar que os detalhes do usuário estão no banco de dados. Então, precisamos criar uma nova classe User e implementar métodos UserInterface para obter os detalhes do usuário do banco de dados. Assim que os dados estiverem disponíveis, o sistema de segurança os usa para permitir / negar o usuário. Symfony fornece uma implementação de usuário padrão para o provedor de memória. Algoritmo é usado para descriptografar a senha do usuário.

Step 6 - Criptografe a senha do usuário usando bcryptalgoritmo e coloque-o no arquivo de configuração. Desde que usamosbcryptalgoritmo, o objeto Usuário tenta descriptografar a senha especificada no arquivo de configuração e, em seguida, tenta corresponder à senha inserida pelo usuário. O aplicativo de console Symfony fornece um comando simples para criptografar a senha.

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 - Use o comando para gerar a senha criptografada e atualizá-la no arquivo de configuração.

# 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- Agora, aplique a segurança a alguma seção do aplicativo. Por exemplo, restrinja a seção admin aos usuários na função, ROLE_ADMIN.

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

Step 9 - Adicione uma página de administração em DefaultController da seguinte maneira.

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

Step 10- Finalmente, acesse a página de administração para verificar a configuração de segurança em um navegador. O navegador solicitará o nome de usuário e a senha e permitirá apenas usuários configurados.

Resultado

Fluxo de Trabalho

Fluxo de trabalho é um conceito avançado que pode ser usado em muitos aplicativos corporativos. Em um aplicativo de comércio eletrônico, o processo de entrega do produto é um fluxo de trabalho. O produto é primeiro faturado (criação do pedido), adquirido na loja e embalado (embalagem / pronto para envio) e despachado para o usuário. Se houver algum problema, o produto retorna do usuário e o pedido é revertido. A ordem do fluxo da ação é muito importante. Por exemplo, não podemos entregar um produto sem faturamento.

O componente Symfony fornece uma maneira orientada a objetos para definir e gerenciar um fluxo de trabalho. Cada etapa de um processo é chamadaplace e a ação necessária para ir de um lugar para outro é chamada transition. A coleção de lugares e transição para criar um fluxo de trabalho é chamada deWorkflow definition.

Vamos entender o conceito de fluxo de trabalho criando um aplicativo simples para gerenciamento de licenças.

Step 1 - Crie um novo aplicativo, workflow-example.

cd /path/to/dev 
mkdir workflow-example 

cd workflow-example 
composer require symfony/workflow

Step 2 - Crie uma nova classe, Leave tendo applied_by, leave_on e status atributos.

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

Aqui, apply_by se refere aos funcionários que desejam sair. leave_on refere-se à data de licença. status refere-se ao status de licença.

Step 3 - O gerenciamento de licenças tem quatro vagas, aplicadas, em processo e aprovadas / rejeitadas.

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

Aqui, criamos uma nova definição usando DefinitionBuilder e lugares adicionados usando addPlaces método.

Step 4 - Defina as ações necessárias para se deslocar de um lugar para outro.

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

Aqui, temos três transições, to_process, approve e reject. a transição to_process aceita o aplicativo de saída e move o local de aplicado para in_process. aprovar transição aprova o pedido de licença e move o local para aprovado. Da mesma forma, rejeitar transição rejeita o pedido de licença e muda o local para rejeitado. Criamos todas as transições usando o método addTransition.

Step 5 - Construir a definição usando o método de construção.

$definition = $builder->build();

Step 6 - Opcionalmente, a definição pode ser despejada como formato de ponto graphviz, que pode ser convertido em arquivo de imagem para fins de referência.

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

Step 7 - Crie um armazenamento de marcação, que é usado para armazenar os locais / status atuais do objeto.

$marking = new SingleStateMarkingStore('status');

Aqui, nós usamos SingleStateMarkingStorepara criar a marca e marcar o status atual na propriedade de status do objeto. Em nosso exemplo, o objeto é Deixar o objeto.

Step 8 - Crie o fluxo de trabalho usando definição e marcação.

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

Aqui, nós usamos Workflow classe para criar o fluxo de trabalho.

Step 9 - Adicione o fluxo de trabalho ao registro da estrutura de fluxo de trabalho usando Registry classe.

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

Step 10 - Por fim, use o fluxo de trabalho para descobrir se uma determinada transição é aplicada usando can método e se assim for, applya transição usando o método de aplicação. Quando uma transição é aplicada, o status do objeto se move de um lugar para outro.

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

A codificação completa é a seguinte -

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