跳到内容

架构

编辑此页

您是我的英雄!谁能想到在完成前两部分之后,您还会在这里?您的努力很快就会得到回报。前两部分没有深入探讨框架的架构。正因为架构使 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 应用以三种环境开始:devprodtest。您可以使用特殊的 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 文档,选择您想要的任何指南。

本作品,包括代码示例,均根据 Creative Commons BY-SA 3.0 许可协议获得许可。
TOC
    版本