Symfony - 高级概念

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

HTTP 缓存

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

缓存项

缓存项是存储为键/值对的单个信息单元。 应为字符串, 可以是任何 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 是禁用的。我们可以使用注册方法启用它。

使用 Symfony\Component\Debug\ErrorHandler;
使用 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.xmlweb_profile 部分 下配置 Web 配置文件组件。

web_profiler: 
   toolbar:      false 
   position:     bottom 

Symfony 应用程序将分析数据作为单独的部分显示在页面底部。

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(以及像 profiler 这样的开发工具)。

步骤 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' 

我们添加了两个用户,user 属于角色 ROLE_USER,admin 属于角色 ROLE_ADMIN。

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

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

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

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

步骤 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 − 使用命令生成加密密码并在配置文件中更新。

# 要开始使用安全性,请查看文档:
# 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: 
            # 禁用资产和分析器的身份验证,
            # 根据您的需要进行调整
         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' } 

Step 9 − Add an admin page in DefaultController as follows.

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

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

结果

Connecting Admin Section

工作流

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

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

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

步骤 1 − 创建一个新的应用程序,workflow-example

cd /path/to/dev 
mkdir workflow-example 

cd workflow-example 
composer require symfony/workflow

步骤 2 − 创建一个新类 Leave,其具有 applied_by、leave_on 和 status 属性。

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

此处,applyed_by 指的是想要休假的员工。leave_on 指的是休假日期。status 指的是休假状态。

步骤 3 − 休假管理有四个位置,applyed、in_process 和 approved/rejected。

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。approve 转换批准请假申请,并将位置移至 approved。类似地,reject 转换拒绝请假申请,并将位置移至 denied。我们已经使用 addTransition 方法创建了所有转换。

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

$definition = $builder->build();

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

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

步骤 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 方法 apply 转换。应用转换后,对象的状态会从一个地方移动到另一个地方。

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

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

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

完整编码如下 −

<?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') . "
"; 
   echo "Can we approve the start process now? " . $workflow->can($leave, 'to_process') . "
"; 
   
   $workflow->apply($leave, 'to_process');  
   echo "Can we approve the leave now? " . $workflow->can($leave, 'approve') . "
"; 
   echo $leave->status . "
"; 
   
   $workflow->apply($leave, 'approve'); 
   echo $leave->status . "
";  
?>  

结果

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