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 − 使用适配器的 getItem 和 set 方法创建缓存项。getItem 使用其键获取缓存项。如果键不存在,则创建一个新项。 set 方法存储实际数据。
$usercache = $cache->getitem('item.users'); $usercache->set(['jon', 'peter']); $cache->save($usercache);
步骤 5 − 使用 getItem、isHit 和 get 方法访问缓存项。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 提供了两个类,ErrorHandler 和 ExceptionHandler 用于调试目的。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.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(以及像 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 − 最后,访问管理页面以在浏览器中检查安全配置。浏览器将要求输入用户名和密码,并且只允许配置的用户。
结果
工作流
工作流是一个在许多企业应用程序中都有使用的高级概念。在电子商务应用程序中,产品交付过程是一个工作流。产品首先开票(订单创建),从商店采购并包装(包装/准备发货),然后发送给用户。如果有任何问题,产品将从用户那里退回,订单将被恢复。行动流程的顺序非常重要。例如,没有账单我们就无法交付产品。
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、approve 和 reject。 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);
步骤 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