Symfony - 高级概念


在本章中,我们将学习 Symfony 框架中的一些高级概念。

HTTP缓存

Web 应用程序中的缓存可提高性能。例如,购物车 Web 应用程序中的热门产品可以缓存有限的时间,以便可以快速地将其呈现给客户,而无需访问数据库。以下是Cache的一些基本组件。

缓存项目

缓存项是存储为键/值对的单个信息单元。键应该是字符串,值可以是任何PHP对象。PHP 对象通过序列化存储为字符串,并在读取项目时转换回对象。

缓存适配器

缓存适配器是在商店中存储项目的实际机制。存储可以是内存、文件系统、数据库、redis等。缓存组件提供AdapterInterface,适配器可以通过该接口将缓存项存储在后端存储中。有很多可用的内置缓存适配器。其中很少如下 -

  • 数组缓存适配器 - 缓存项存储在 PHP 数组中。

  • 文件系统缓存适配器 - 缓存项存储在文件中。

  • PHP 文件缓存适配器 - 缓存项目存储为 php 文件。

  • APCu 缓存适配器 - 使用 PHP APCu 扩展将缓存项存储在共享内存中。

  • Redis 缓存适配器 - 缓存项存储在 Redis 服务器中。

  • PDO 和 Doctrine DBAL 缓存适配器 - 缓存项存储在数据库中。

  • 链式缓存适配器 - 组合多个缓存适配器以进行复制。

  • 代理缓存适配器 - 缓存项使用第三方适配器存储,该适配器实现 CacheItemPoolInterface。

缓存池

缓存池是缓存项的逻辑存储库。缓存池是由缓存适配器实现的。

简单的申请

让我们创建一个简单的应用程序来理解缓存概念。

步骤 1 - 创建一个新应用程序,cache-example

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

步骤 2 - 安装缓存组件。

composer require symfony/cache

步骤 3 - 创建文件系统适配器。

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

步骤 4 - 使用适配器的getItemset方法创建缓存项。getItem 使用其键获取缓存项。如果密钥不存在,则会创建一个新项目。set方法存储实际数据。

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

步骤 5 - 使用getItem、isHitget方法访问缓存项。isHit 通知缓存项的可用性,而 get 方法提供实际数据。

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

步骤 6 - 使用deleteItem方法删除缓存项。

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

完整的代码清单如下。

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

结果

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

调试

调试是开发应用程序时最常见的活动之一。Symfony 提供了一个单独的组件来简化调试过程。我们可以通过调用Debug类的enable方法来启用Symfony调试工具。

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

Symfony 提供了两个类,ErrorHandlerExceptionHandler用于调试目的。ErrorHandler 捕获 PHP 错误并将其转换为异常、ErrorException 或 FatalErrorException,而 ExceptionHandler 捕获未捕获的 PHP 异常并将其转换为有用的 PHP 响应。默认情况下禁用 ErrorHandler 和 ExceptionHandler。我们可以使用register方法来启用它。

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

在 Symfony Web 应用程序中,调试环境由 DebugBundle 提供。在 AppKernel 的registerBundles方法中注册该捆绑包以启用它。

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

分析器

应用程序的开发需要世界一流的分析工具。分析工具收集有关应用程序的所有运行时信息,例如执行时间、各个模块的执行时间、数据库活动所花费的时间、内存使用情况等。Web 应用程序需要更多信息,例如请求时间、除了上述指标之外,创建响应所花费的时间等。

Symfony 默认在 Web 应用程序中启用所有此类信息。Symfony 为 Web 分析提供了一个单独的包,称为WebProfilerBundle。通过在 AppKernel 的 registerBundles 方法中注册捆绑包,可以在 Web 应用程序中启用 Web 分析器捆绑包。

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

可以在应用程序配置文件app/config/config.xml的web_profile 部分下配置 Web 配置文件组件

web_profiler: 
   toolbar:      false 
   position:     bottom 

Symfony 应用程序将分析数据显示在页面底部作为一个不同的部分。

交响乐应用程序

Symfony 还提供了一种简单的方法,使用DataCollectorInterface 接口和 twig 模板在配置文件数据中添加有关页面的自定义详细信息。简而言之,Symfony 使 Web 开发人员能够相对轻松地提供出色的分析框架来创建世界一流的应用程序。

安全

如前所述,Symfony 通过其安全组件提供了强大的安全框架。安全组件分为以下四个子组件。

  • symfony/security-core - 核心安全功能。
  • symfony/security-http - HTTP 协议中的集成安全功能。
  • symfony/security-csrf - 防止 Web 应用程序中的跨站点请求伪造。
  • symfony/security-acl - 基于高级访问控制列表的安全框架。

简单的认证和授权

让我们使用一个简单的演示应用程序来学习身份验证和授权的概念。

步骤 1 -使用以下命令创建一个新的 Web 应用程序securitydemo 。

 symfony new securitydemo

步骤 2 - 使用安全配置文件在应用程序中启用安全功能。安全相关的配置放置在单独的文件security.yml中。默认配置如下。

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

默认配置启用基于内存的安全提供程序和对所有页面的匿名访问。防火墙部分从安全框架中排除与模式^/(_(profiler|wdt)|css|images|js)/匹配的文件。默认模式包括样式表、图像和 JavaScript(以及分析器等开发工具)。

步骤 3 - 通过在主要部分添加 http_basic 选项来启用基于 HTTP 的安全认证系统,如下所示。

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

步骤 4 - 在内存提供者部分添加一些用户。另外,为用户添加角色。

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

我们添加了两个用户,角色 ROLE_USER 中的用户和角色 ROLE_ADMIN 中的管理员。

步骤 5 - 添加编码器以获取当前登录用户的完整详细信息。编码器的目的是从 Web 请求中获取当前用户对象的完整详细信息。

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

Symfony 提供了一个接口UserInterface来获取用户详细信息,例如用户名、角色、密码等。我们需要根据我们的要求实现该接口并在编码器部分中配置它。

例如,让我们考虑用户详细信息位于数据库中。然后,我们需要创建一个新的 User 类并实现 UserInterface 方法以从数据库获取用户详细信息。一旦数据可用,安全系统就会使用它来允许/拒绝用户。Symfony 为内存提供者提供了默认的用户实现。算法用于解密用户密码。

步骤 6 - 使用bcrypt算法加密用户密码并将其放入配置文件中。由于我们使用了bcrypt算法,User 对象会尝试解密配置文件中指定的密码,然后尝试与用户输入的密码进行匹配。Symfony 控制台应用程序提供了一个简单的命令来加密密码。

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 

步骤 7 - 使用命令生成加密密码并在配置文件中更新它。

# 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: ~             

步骤 8 - 现在,将安全性应用到应用程序的某些部分。例如,将管理部分限制为角色 ROLE_ADMIN 中的用户。

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

步骤 9 - 在 DefaultController 中添加管理页面,如下所示。

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

步骤 10 - 最后,访问管理页面以检查浏览器中的安全配置。浏览器将要求输入用户名和密码,并且仅允许配置的用户。

结果

正在连接

行政部分

工作流程

工作流是一个先进的概念,在许多企业应用程序中都有使用。在电子商务应用程序中,产品交付过程是一个工作流程。产品首先被计费(创建订单),从商店采购并包装(包装/准备发货),然后发货给用户。如果出现任何问题,产品将被用户退回并恢复订单。动作流程的顺序非常重要。例如,我们无法在没有计费的情况下交付产品。

Symfony 组件提供了一种面向对象的方式来定义和管理工作流程。流程中的每个步骤称为位置,从一个位置移动到另一个位置所需的操作称为转换。创建工作流的位置和转换的集合称为工作流定义

让我们通过创建一个简单的休假管理应用程序来理解工作流的概念。

步骤 1 - 创建一个新应用程序,工作流程示例

cd /path/to/dev 
mkdir workflow-example 

cd workflow-example 
composer require symfony/workflow

步骤 2 - 创建一个新类,Leave具有Applied_by、leave_onstatus属性。

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

这里,apply_by 指的是要请假的员工。leave_on 指休假日期。status 指的是休假状态。

步骤 3 - 休假管理有四个位置:已申请、正在处理和已批准/拒绝。

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

在这里,我们使用DefinitionBuilder创建了一个新定义,并使用addPlaces方法添加了位置。

步骤 4 - 定义从一个地方移动到另一个地方所需的操作。

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

在这里,我们有三个转换:to_process、approvereject。to_process 转换接受请假申请并将位置从 Applied 移至 in_process。批准过渡批准休假申请并将地点移至已批准。同样,拒绝转换会拒绝休假申请并将位置移至拒绝。我们使用 addTransition 方法创建了所有转换。

步骤 5 - 使用构建方法构建定义。

$definition = $builder->build();

步骤 6 - 或者,可以将定义转储为 graphviz 点格式,可以将其转换为图像文件以供参考。

$dumper = new GraphvizDumper(); 
echo $dumper->dump($definition);
Graphviz 点格式

步骤 7 - 创建一个标记存储,用于存储对象的当前位置/状态。

$marking = new SingleStateMarkingStore('status');

在这里,我们使用SingleStateMarkingStore类来创建标记,并将当前状态标记到对象的 status 属性中。在我们的示例中,该对象是 Leave 对象。

步骤 8 - 使用定义和标记创建工作流程。

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

在这里,我们使用Workflow类来创建工作流。

步骤 9 - 使用Registry类将工作流添加到工作流框架的注册表中。

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

步骤 10 - 最后,使用工作流程查找是否使用can方法应用给定的转换,如果是,则使用apply 方法应用转换。应用过渡时,对象的状态从一个位置移动到另一位置。

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

完整的编码如下 -

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

结果

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