Skip to main content

架构

你是我的英雄!谁会想到在前两部分之后你还会在这里?你的努力很快就会得到丰厚的回报。前两部分并没有太深入地研究框架的架构。因为它使 Symfony 从众多框架中脱颖而出,所以现在让我们深入了解该架构。

添加日志记录

新的 Symfony 应用程序是微型的:它基本上只是一个路由和控制器系统。但多亏了 Flex,安装更多功能变得很简单。

想要一个日志系统吗?没问题:

composer require logger

这将安装并配置(通过配方)强大的Monolog库。要在控制器中使用记录器,请添加一个带有LoggerInterface类型提示的新参数:

// 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 。可以通过更新配方添加的配置文件之一来配置日志文件路径甚至不同的日志记录方法。

服务和自动装配

但是等等!刚刚发生了一件非常酷的事情。 Symfony 读取LoggerInterface类型提示并自动发现它应该向我们传递 Logger 对象!这称为自动装配。

Symfony 应用程序中完成的每一点工作都是由一个对象完成的:Logger 对象记录事物,Twig 对象呈现模板。这些对象称为服务,它们是帮助您构建丰富功能的工具。

为了让生活变得更精彩,您可以要求 Symfony 通过使用类型提示来向您传递服务。您还可以使用哪些其他可能的类或接口?通过运行找出:

php bin/console debug:autowiring

  

  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”等)。不要将此代码直接放入控制器中,而是创建一个新类:

// src/GreetingGenerator.php
namespace App;

class GreetingGenerator
{
    public function getRandomGreeting(): string
    {
        $greetings = ['Hey', 'Yo', 'Aloha'];
        $greeting = $greetings[array_rand($greetings)];

        return $greeting;
    }
}

伟大的!您可以立即在控制器中使用它:

// 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并将其作为参数传递。但是,我们是否也可以将记录器逻辑移至GreetingGenerator ?是的!您可以在服务内使用自动装配来访问其他服务。唯一的区别是它是在构造函数中完成的:

<?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的类:

// 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!";
    }
}

创建一个文件后,您可以立即使用它:

{# 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/目录中的配置文件中定义特定环境的选项:

# config/packages/routing.yaml
framework:
    router:
        utf8: true

when@prod:
    framework:
        router:
            strict_requirements: null

这是一个强大的想法:通过更改一项配置(环境),您的应用程序将从易于调试的体验转变为速度优化的体验。

哦,那要怎么改变环境呢?将APP_ENV环境变量从dev更改为prod :

# .env
- APP_ENV=dev
+ APP_ENV=prod

但接下来我想更多地谈谈环境变量。将值更改回dev :当您在本地工作时,调试工具非常有用。

环境变量

每个应用程序都包含每个服务器上不同的配置 - 例如数据库连接信息或密码。这些应该如何保存?在文件中?或者其他方式?

Symfony 遵循行业最佳实践,将基于服务器的配置存储为环境变量。这意味着 Symfony 可以与平台即服务 (PaaS) 部署系统以及 Docker完美配合。

但在开发时设置环境变量可能会很痛苦。这就是您的应用程序自动加载.env文件的原因。然后,该文件中的键将成为环境变量并由您的应用程序读取:

# .env
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=cc86c7ca937636d5ddf1b754beb22a10
###< symfony/framework-bundle ###

起初,该文件包含的内容并不多。但随着您的应用程序的增长,您将根据需要添加更多配置。但实际上,它变得更有趣!假设您的应用程序需要数据库 ORM。让我们安装 Doctrine ORM:

composer require doctrine

感谢 Flex 安装的新配方,再次查看.env文件:

###> 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 中的所有内容都经过精心设计,不会妨碍您,以便您可以继续编码和添加功能,所有这些都满足您所需的速度和质量。