【问题标题】:Integrate Symfony's Dependency Injection (autowiring) into legacy app将 Symfony 的依赖注入(自动装配)集成到遗留应用程序中
【发布时间】:2020-07-08 21:00:32
【问题描述】:

我有一个遗留应用程序,它建立在一个旧的自定义 MVC 框架上,我希望最终摆脱它。这个框架不依赖单一的前端控制器,所以大部分页面还是有专门的php文件来调用尊敬的控制器,其他的都是php/html混合的。我已经阅读了有关使用各种方法 (https://symfony.com/doc/current/migration.html) 将应用程序迁移到 symfony 的文章,但是我对这两种方法都遇到了问题,并且意识到我真的不需要 symfony 的路由处理。

Symfony 当前存在于我们的应用程序中,但仅由各种命令使用。我们所有的核心逻辑仍在遗留应用程序中,因此 Symfony 可以毫无问题地访问它,因为类都在全局命名空间中。然而,问题是,遗留应用程序不能使用任何新的 Symfony 类,因为它不支持依赖注入。为了开始将我们的一些核心逻辑和功能转移到 Symfony,需要这种能力。

理想情况下,我希望能够将容器加载到我们的旧应用程序中,该应用程序提供了我们所有的自动装配服务。允许我在旧版应用程序中访问基于 Symfony 的新服务。

非常感谢任何帮助。

非常感谢。

更新 1

所以我尝试了@Cerad 所说的,只需访问内核,因为它是全局的。我将 bootstrap.php 逻辑复制到我的旧应用程序的主配置中(因此它会加载现有的 .env* 文件),然后启动内核(实例化它并在我的旧配置中调用 boot)。它有效,我可以在我的 php 文件中引用 $kernel(使用全局 $kernel)并访问容器。但是,这与@Dmitry Solovov 的回复一致,服务必须是公开的。

我必须将我想要的所有服务设置为公开吗?如果我在 services.yaml 中手动定义服务,将其设置为 public,它就可以工作。

但这并不理想,因为我想自动加载我的服务,因此我可以正确使用服务,而不必明确定义我希望在旧版应用中可用的每个服务。

如何在不公开服务的情况下将服务注入旧版控制器?就像 Symfony 的控制器如何允许您将服务注入到控制器方法中一样?

非常感谢。

【问题讨论】:

  • 容器可以通过 $kernel 访问,而 $kernel 又是一个全局变量。这是你要求的吗?
  • 服务必须是公开的,实际上没有办法将非公开服务注入您的应用程序控制器。我用一个示例编辑了我的答案,以公开所有服务。
  • 用一个简单的例子开始一个新问题可能会更好。起初你说你的遗留类不支持注入,然后,通过你的编辑,你说你的遗留控制器支持。然后在最后一句话中,您询问注入控制器方法,这是一个完全不同的主题,而不是容器本身所做的事情。术语很重要。有一个 Reddit Symfony 论坛可能是解决这类问题的更好平台。
  • 很抱歉给您带来了困惑。起初他们没有,但是您对仅使用 $kernel 的评论使我仅在我的主配置中加载内核。那时我能够访问所有公共服务。谢谢你的帮助,你让我走上了正确的道路。

标签: php symfony symfony4


【解决方案1】:

独立使用依赖注入:

  • 安装包:
composer require symfony/dependency-injection
  • 在配置文件中定义您的服务(即src/Resources/config/services.yaml)。示例:
services:
    _defaults:
        autowire: true
        autoconfigure: true
        public: false

    App\Services\MyService:
        class: App\Services\MyService
        public: true

您也可以使用服务自动导入功能https://symfony.com/doc/current/service_container.html#importing-many-services-at-once-with-resource

  • 使用以下代码编译 DI 容器:
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/src/Resources/config'));
$loader->load('services.yaml');
$container->compile();
  • 将此容器实例注入您的应用程序。或者扩展 ContanerBuilder 类并使其成为单例。

在您的应用程序中使用的服务应该是公开的,因此您可以直接从容器中获取它们:

$service = $container->get(\App\Services\MyService::class);

您还可以默认公开所有服务:

services:
    _defaults:
        public: true

【讨论】:

  • 这种做法我大致就是这样做的;然而,由于我的项目有遗留应用程序和 symfony 并排运行,我能够只使用现有的 services.yaml 文件,而不是使用 $container->complile(),我只是实例化内核并运行 boot() .
  • 虽然我没有公开所有服务,因为我不希望我现有的 Symfony 应用程序走上这条糟糕的道路。相反,我创建了遗留服务,并将其设置为公共。然后这些服务注入了我希望我的遗留应用程序可用的各种服务。因为我在技术上并不要求所有服务都可用,并且我想继续使用一些遗留的核心逻辑。例如,我们希望将旧的 AWS S3 代码替换为基于 symfony 的 AWS S3 代码。这为我们提供了这条道路。非常感谢。
【解决方案2】:

我有一个加载在所有页面上的单一配置,这就是我复制引导逻辑并加载内核的地方:

// ***** legacy config code above

// This probably could just be loaded using require, but kept it here for completeness
if (is_array($env = @include dirname(__DIR__).'/.env.local.php')) {
    foreach ($env as $k => $v) {
        $_ENV[$k] = $_ENV[$k] ?? (isset($_SERVER[$k]) && 0 !== strpos($k, 'HTTP_') ? $_SERVER[$k] : $v);
    }
} elseif (!class_exists(Dotenv::class)) {
    throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.');
} else {
    // load all the .env files
    (new Dotenv(false))->loadEnv(dirname(__DIR__).'/config/.env');
}

$_SERVER += $_ENV;
$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev';
$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'dev' == $_SERVER['APP_ENV'];
$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0';
// End Symfony's bootstrap

// Load Symfony's kernel
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();

从那里我可以访问我想要的特定服务 - 只要它是公开的:

global $kernel;
$service = $kernel->getContainer()->get(\App\Services\MyService::class);

让我的项目运行良好的真正想法不是将所有服务设置为公开,而是创建一个遗留服务,该服务在 services.yaml 中手动定义并设置为公开:

services:
    App\Services\Legacy\AWSLegacy:
        public: true

AWSLegacy 看起来像:

namespace App\Services\Legacy;

use App\Services\AWS\S3;

class AWSLegacy
{
    /** @var S3 */
    public $s3;

    public function __construct(
        S3 $s3
    )
    {
        $this->s3 = $s3;
    }
}

这使我可以将我希望在旧应用程序中可用的类似服务组合在一起,而无需在 services.yaml 中手动为每个服务创建引用并将它们设置为公开。

Symfony 桥接方法对我不起作用,因为我不希望 Symfony 在我的旧应用程序中处理路由(请求和响应),我只想访问新服务。

感谢 @Cerad 和 @Dmitry Solovov 的帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-06-01
    • 2020-03-08
    • 1970-01-01
    • 2014-11-15
    • 2013-05-03
    • 2018-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多