架构
您是我的英雄!谁能想到在完成前两部分之后,您还会在这里?您的努力很快就会得到回报。前两部分没有深入探讨框架的架构。正因为架构使 Symfony 在众多框架中脱颖而出,现在让我们深入了解架构。
添加日志记录
一个新的 Symfony 应用是微型的:它基本上只是一个路由和控制器系统。但得益于 Flex,安装更多功能非常简单。
想要日志记录系统?没问题
1
$ composer require logger
这将安装并配置(通过 recipe)强大的 Monolog 库。要在控制器中使用 logger,请添加一个类型提示为 LoggerInterface
的新参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Controller/DefaultController.php
namespace App\Controller;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class DefaultController extends AbstractController
{
#[Route('/hello/{name}', methods: ['GET'])]
public function index(string $name, LoggerInterface $logger): Response
{
$logger->info("Saying hello to $name!");
// ...
}
}
就是这样!新的日志消息将被写入 var/log/dev.log
。可以通过更新 recipe 添加的配置文件之一来配置日志文件路径,甚至可以配置不同的日志记录方法。
服务 & 自动装配
但是等等!刚刚发生了一件非常酷的事情。Symfony 读取了 LoggerInterface
类型提示,并自动识别出它应该将 Logger 对象传递给我们!这被称为自动装配。
在 Symfony 应用中完成的每一项工作都是由一个对象完成的:Logger 对象记录日志,Twig 对象渲染模板。这些对象被称为服务,它们是帮助您构建丰富功能的工具。
为了让生活更美好,您可以请求 Symfony 通过使用类型提示来传递服务。您还可以使用哪些其他可能的类或接口?通过运行以下命令来找出答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14
$ php bin/console debug:autowiring
# this is just a *small* sample of the output...
Describes a logger instance.
Psr\Log\LoggerInterface - alias:monolog.logger
Request stack that controls the lifecycle of requests.
Symfony\Component\HttpFoundation\RequestStack - alias:request_stack
RouterInterface is the interface that all Router classes must implement.
Symfony\Component\Routing\RouterInterface - alias:router.default
[...]
这只是完整列表的简短摘要!随着您添加更多软件包,此工具列表将增长!
创建服务
为了保持代码的组织性,您甚至可以创建自己的服务!假设您想要生成一个随机问候语(例如“Hello”、“Yo”等)。与其将此代码直接放在控制器中,不如创建一个新类
1 2 3 4 5 6 7 8 9 10 11 12 13
// src/GreetingGenerator.php
namespace App;
class GreetingGenerator
{
public function getRandomGreeting(): string
{
$greetings = ['Hey', 'Yo', 'Aloha'];
$greeting = $greetings[array_rand($greetings)];
return $greeting;
}
}
太棒了!您可以立即在控制器中使用它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// src/Controller/DefaultController.php
namespace App\Controller;
use App\GreetingGenerator;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class DefaultController extends AbstractController
{
#[Route('/hello/{name}', methods: ['GET'])]
public function index(string $name, LoggerInterface $logger, GreetingGenerator $generator): Response
{
$greeting = $generator->getRandomGreeting();
$logger->info("Saying $greeting to $name!");
// ...
}
}
就是这样!Symfony 将自动实例化 GreetingGenerator
并将其作为参数传递。但是,我们是否也可以将 logger 逻辑移至 GreetingGenerator
?是的!您可以在服务内部使用自动装配来访问其他服务。唯一的区别是在构造函数中完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
<?php
// src/GreetingGenerator.php
+ use Psr\Log\LoggerInterface;
class GreetingGenerator
{
+ public function __construct(
+ private LoggerInterface $logger,
+ ) {
+ }
public function getRandomGreeting(): string
{
// ...
+ $this->logger->info('Using the greeting: '.$greeting);
return $greeting;
}
}
是的!这也有效:无需配置,不浪费时间。继续编码吧!
Twig 扩展 & 自动配置
得益于 Symfony 的服务处理,您可以通过多种方式扩展 Symfony,例如通过为复杂的授权规则创建事件订阅器或安全投票器。让我们向 Twig 添加一个名为 greet
的新过滤器。如何操作?创建一个扩展 AbstractExtension
的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
// src/Twig/GreetExtension.php
namespace App\Twig;
use App\GreetingGenerator;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class GreetExtension extends AbstractExtension
{
public function __construct(
private GreetingGenerator $greetingGenerator,
) {
}
public function getFilters(): array
{
return [
new TwigFilter('greet', [$this, 'greetUser']),
];
}
public function greetUser(string $name): string
{
$greeting = $this->greetingGenerator->getRandomGreeting();
return "$greeting $name!";
}
}
仅创建一个文件后,您就可以立即使用它
1 2 3
{# templates/default/index.html.twig #}
{# Will print something like "Hey Symfony!" #}
<h1>{{ name|greet }}</h1>
这是如何工作的?Symfony 注意到您的类扩展了 AbstractExtension
,因此自动将其注册为 Twig 扩展。这称为自动配置,它适用于很多很多事情。创建一个类,然后扩展一个基类(或实现一个接口)。Symfony 会处理剩下的事情。
极速:缓存容器
在看到 Symfony 自动处理了这么多事情之后,您可能会想:“这不会损害性能吗?” 实际上,不会!Symfony 非常快。
这怎么可能呢?服务系统由一个非常重要的对象“容器”管理。大多数框架都有容器,但 Symfony 的容器是独一无二的,因为它被缓存了。当您加载第一个页面时,所有服务信息都被编译和保存。这意味着自动装配和自动配置功能不会增加任何开销!这也意味着您会获得出色的错误信息:Symfony 在构建容器时会检查和验证所有内容。
现在您可能想知道,当您更新文件并且缓存需要重建时会发生什么?我喜欢您的想法!它足够智能,可以在下次页面加载时重建。但这确实是下一节的主题。
开发与生产环境
框架的主要工作之一是使调试变得容易!我们的应用为此提供了许多出色的工具:Web 调试工具栏显示在页面底部,错误信息很大、很漂亮且很明确,并且任何配置缓存都会在需要时自动重建。
但是当您部署到生产环境时呢?我们将需要隐藏这些工具并优化速度!
这可以通过 Symfony 的环境系统来解决。Symfony 应用以三种环境开始:dev
、prod
和 test
。您可以使用特殊的 when@
关键字在 config/
目录中的配置文件中为特定环境定义选项
1 2 3 4 5 6 7 8 9
# config/packages/routing.yaml
framework:
router:
utf8: true
when@prod:
framework:
router:
strict_requirements: null
这是一个强大的想法:通过更改一个配置(环境),您的应用将从调试友好的体验转变为针对速度优化的体验。
哦,如何更改环境?将 APP_ENV
环境变量从 dev
更改为 prod
1 2 3
# .env
- APP_ENV=dev
+ APP_ENV=prod
但接下来我想更多地谈谈环境变量。将值改回 dev
:当您在本地工作时,调试工具非常有用。
环境变量
每个应用都包含在每台服务器上都不同的配置 - 例如数据库连接信息或密码。这些应该如何存储?在文件中?还是另一种方式?
Symfony 遵循行业最佳实践,将基于服务器的配置存储为环境变量。这意味着 Symfony 可以完美地与平台即服务 (PaaS) 部署系统以及 Docker 配合使用。
但是,在开发过程中设置环境变量可能很麻烦。这就是为什么您的应用会自动加载 .env
文件。此文件中的键随后将成为环境变量并被您的应用读取
1 2 3 4 5
# .env
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=cc86c7ca937636d5ddf1b754beb22a10
###< symfony/framework-bundle ###
起初,该文件没有包含太多内容。但是随着您的应用增长,您将根据需要添加更多配置。但是,实际上,它变得更加有趣!假设您的应用需要数据库 ORM。让我们安装 Doctrine ORM
1
$ composer require doctrine
得益于 Flex 安装的新 recipe,再次查看 .env
文件
1 2 3 4 5 6 7 8 9
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=cc86c7ca937636d5ddf1b754beb22a10
###< symfony/framework-bundle ###
+ ###> doctrine/doctrine-bundle ###
+ # ...
+ DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
+ ###< doctrine/doctrine-bundle ###
新的 DATABASE_URL
环境变量已自动添加,并且已被新的 doctrine.yaml
配置文件引用。通过结合环境变量和 Flex,您无需额外努力即可使用行业最佳实践。
继续前进!
您可能会觉得我疯了,但是阅读完这一部分后,您应该对 Symfony 最重要的部分感到满意了。Symfony 中的一切都旨在不妨碍您,以便您可以继续编码和添加功能,所有这些都以您要求的速度和质量进行。
快速导览就到此为止。从身份验证、表单到缓存,还有更多内容等待您去发现。准备好深入研究这些主题了吗?无需再犹豫 - 前往官方 Symfony 文档,选择您想要的任何指南。