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 默认情况下是禁用的。我们可以使用 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(); 
}

Web 探查组件可以在应用配置文件app/config/config.xmlweb_profile 部分中进行配置。

web_profiler: 
   toolbar:      false 
   position:     bottom 

Symfony 应用在页面底部显示探查数据作为单独的部分。

Symfony application

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 - 通过在 main 部分添加 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 为内存提供程序提供了一个默认的用户实现。算法用于解密用户密码。

步骤 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 - 最后,在浏览器中访问管理员页面以检查安全配置。浏览器将要求输入用户名和密码,并且只允许已配置的用户。

结果

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_onstatus属性。

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

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

步骤 3 - 请假管理有四个位置,applied、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 转换拒绝请假申请并将位置移动到 rejected。我们使用 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 方法应用转换。当转换应用时,对象的状态从一个位置移动到另一个位置。

$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
广告