Migrating an Existing Application to Symfony

When you have an existing application that was not built with Symfony, you might want to move over parts of that application without rewriting the existing logic completely. For those cases there is a pattern called Strangler Fig Application. The basic idea of this pattern is to create a new application that gradually takes over functionality from an existing application. This migration approach can be implemented with Symfony in various ways and has some benefits over a rewrite such as being able to introduce new features in the existing application and reducing risk by avoiding a "big bang"-release for the new application.

Symfony で構築されていない既存のアプリケーションがある場合、既存のロジックを完全に書き直すことなく、そのアプリケーションの一部を移動したい場合があります。そのような場合には、Strangler Fig Application と呼ばれるパターンがあります。このパターンの基本的な考え方は、既存のアプリケーションから徐々に機能を引き継ぐ新しいアプリケーションを作成することです。この移行アプローチは、さまざまな方法で Symfony で実装でき、既存のアプリケーションに新機能を導入でき、新しいアプリケーションの「ビッグバン」リリースを回避することでリスクを軽減できるなど、書き直しよりもいくつかの利点があります。

Screencast

スクリーンキャスト

The topic of migrating from an existing application towards Symfony is sometimes discussed during conferences. For example the talk Modernizing with Symfony reiterates some of the points from this page.

既存のアプリケーションから Symfony への移行に関するトピックは、カンファレンスで議論されることがあります。たとえば、Symfony によるトークモダナイズでは、このページのいくつかのポイントを繰り返します。

Prerequisites

Before you start introducing Symfony to the existing application, you have to ensure certain requirements are met by your existing application and environment. Making the decisions and preparing the environment before starting the migration process is crucial for its success.

Symfony を既存のアプリケーションに導入する前に、既存のアプリケーションと環境が特定の要件を満たしていることを確認する必要があります。移行プロセスを開始する前に決定を下し、環境を準備することは、移行プロセスを成功させるために非常に重要です。

Note

ノート

The following steps do not require you to have the new Symfony application in place and in fact it might be safer to introduce these changes beforehand in your existing application.

次の手順では、新しい Symfony アプリケーションを用意する必要はありません。実際、これらの変更を既存のアプリケーションに事前に導入する方が安全な場合があります。

Choosing the Target Symfony Version

Most importantly, this means that you will have to decide which version you are aiming to migrate to, either a current stable release or the long term support version (LTS). The main difference is, how frequently you will need to upgrade in order to use a supported version. In the context of a migration, other factors, such as the supported PHP-version or support for libraries/bundles you use, may have a strong impact as well. Using the most recent, stable release will likely give you more features, but it will also require you to update more frequently to ensure you will get support for bug fixes and security patches and you will have to work faster on fixing deprecations to be able to upgrade.

最も重要なことは、これは、現在の安定版リリースまたは長期サポート バージョン (LTS) のどちらに移行しようとしているかを決定する必要があることを意味します。主な違いは、サポートされているバージョンを使用するために必要なアップグレードの頻度です。移行のコンテキストでは、サポートされている PHP バージョンや使用するライブラリ/バンドルのサポートなど、他の要因も大きな影響を与える可能性があります。最新の安定したリリースを使用すると、より多くの機能が得られる可能性がありますが、バグ修正とセキュリティ パッチのサポートを確実に受けられるように、より頻繁に更新する必要があり、アップグレードできるようにするためには、非推奨の修正に迅速に取り組む必要があります。

Tip

ヒント

When upgrading to Symfony you might be tempted to also use Flex. Please keep in mind that it primarily focuses on bootstrapping a new Symfony application according to best practices regarding the directory structure. When you work in the constraints of an existing application you might not be able to follow these constraints, making Flex less useful.

Symfony にアップグレードするとき、Flex も使用したくなるかもしれません。ディレクトリ構造に関するベストプラクティスに従って、新しい Symfony アプリケーションをブートストラップすることに主に焦点を当てていることに注意してください。既存のアプリケーションの制約の下で作業する場合、これらの制約に従うことができず、Flex の有用性が低下する可能性があります。

First of all your environment needs to be able to support the minimum requirements for both applications. In other words, when the Symfony release you aim to use requires PHP 7.1 and your existing application does not yet support this PHP version, you will probably have to upgrade your legacy project. Use the check:requirements command to check if your server meets the technical requirements for running Symfony applications and compare them with your current application's environment to make sure you are able to run both applications on the same system. Having a test system, that is as close to the production environment as possible, where you can just install a new Symfony project next to the existing one and check if it is working will give you an even more reliable result.

まず、環境が両方のアプリケーションの最小要件をサポートできる必要があります。つまり、使用しようとしている Symfony リリースが PHP 7.1 を必要とし、既存のアプリケーションがまだこの PHP バージョンをサポートしていない場合、おそらくレガシー プロジェクトをアップグレードする必要があります。 check:requirements コマンドを使用して、サーバーが Symfony アプリケーションを実行するための技術的要件を満たしているかどうかを確認し、それらを現在のアプリケーションの環境と比較して、同じシステムで両方のアプリケーションを実行できることを確認します。既存のプロジェクトの隣に新しい Symfony プロジェクトをインストールし、それが機能しているかどうかを確認するだけで、できるだけ本番環境に近いテストシステムを用意すると、さらに信頼性の高い結果が得られます。

Tip

ヒント

If your current project is running on an older PHP version such as PHP 5.x upgrading to a recent version will give you a performance boost without having to change your code.

現在のプロジェクトが PHP 5.x などの古いバージョンの PHP で実行されている場合、最新バージョンにアップグレードすると、コードを変更しなくてもパフォーマンスが向上します。

Setting up Composer

Another point you will have to look out for is conflicts between dependencies in both applications. This is especially important if your existing application already uses Symfony components or libraries commonly used in Symfony applications such as Doctrine ORM or Twig. A good way for ensuring compatibility is to use the same composer.json for both project's dependencies.

注意が必要なもう 1 つの点は、両方のアプリケーションの依存関係間の競合です。これは、既存のアプリケーションが Doctrine ORM や Twig などの Symfony アプリケーションで一般的に使用される Symfony コンポーネントまたはライブラリを既に使用している場合に特に重要です。互換性を確保する良い方法は、両方のプロジェクトの依存関係に同じ composer.json を使用することです。

Once you have introduced composer for managing your project's dependencies you can use its autoloader to ensure you do not run into any conflicts due to custom autoloading from your existing framework. This usually entails adding an autoload-section to your composer.json and configuring it based on your application and replacing your custom logic with something like this:

プロジェクトの依存関係を管理するために composer を導入したら、そのオートローダーを使用して、既存のフレームワークからのカスタム オートロードが原因で競合が発生しないようにすることができます。これには通常、composer.json に autoload-section を追加し、アプリケーションに基づいて構成し、カスタム ロジックを次のようなものに置き換える必要があります。
1
require __DIR__.'/vendor/autoload.php';

Removing Global State from the Legacy Application

In older PHP applications it was quite common to rely on global state and even mutate it during runtime. This might have side effects on the newly introduced Symfony application. In other words code relying on globals in the existing application should be refactored to allow for both systems to work simultaneously. Since relying on global state is considered an anti-pattern nowadays you might want to start working on this even before doing any integration.

古い PHP アプリケーションでは、グローバルな状態に依存し、実行時にそれを変更することさえ一般的でした。これは、新しく導入された Symfony アプリケーションに副作用をもたらす可能性があります。つまり、既存のアプリケーションで globals に依存するコードは、両方のシステムが同時に動作できるようにリファクタリングする必要があります。現在、グローバル状態に依存することはアンチパターンと見なされているため、統合を行う前に、これに取り組み始めたいと思うかもしれません。

Setting up the Environment

There might be additional steps you need to take depending on the libraries you use, the original framework your project is based on and most importantly the age of the project as PHP itself underwent many improvements throughout the years that your code might not have caught on to, yet. As long as both your existing code and a new Symfony project can run in parallel on the same system you are on a good way. All these steps do not require you to introduce Symfony just yet and will already open up some opportunities for modernizing your existing code.

使用するライブラリ、プロジェクトが基づいている元のフレームワーク、そして最も重要なこととして、PHP 自体が何年にもわたって多くの改善を経てきたため、コードがまだ理解されていない可能性があるため、プロジェクトの年齢によっては、追加の手順が必要になる場合があります。既存のコードと新しい Symfony プロジェクトの両方が同じシステムで並行して実行できる限り、順調です。これらのすべてのステップでは、まだ Symfony を導入する必要はなく、既存のコードをモダナイズする機会がすでにいくつか開かれています。

Establishing a Safety Net for Regressions

Before you can safely make changes to the existing code, you must ensure that nothing will break. One reason for choosing to migrate is making sure that the application is in a state where it can run at all times. The best way for ensuring a working state is to establish automated tests.

既存のコードを安全に変更する前に、何も壊れないことを確認する必要があります。移行を選択する理由の 1 つは、アプリケーションが常に実行できる状態であることを確認することです。動作状態を保証する最善の方法は、自動化されたテストを確立することです。

It is quite common for an existing application to either not have a test suite at all or have low code coverage. Introducing unit tests for this code is likely not cost effective as the old code might be replaced with functionality from Symfony components or might be adapted to the new application. Additionally legacy code tends to be hard to write tests for, making the process slow and cumbersome.

既存のアプリケーションにテスト スイートがまったくないか、コード カバレッジが低いことはよくあることです。このコードに単体テストを導入することは、古いコードが Symfony コンポーネントの機能に置き換えられるか、新しいアプリケーションに適応される可能性があるため、費用対効果が低い可能性があります.さらに、レガシーコードはテストを書くのが難しく、プロセスが遅く面倒になります.

Instead of providing low level tests, that ensure each class works as expected, it might makes sense to write high level tests ensuring that at least anything user facing works on at least a superficial level. These kinds of tests are commonly called End-to-End tests, because they cover the whole application from what the user sees in the browser down to the very code that is being run and connected services like a database. To automate this you have to make sure that you can get a test instance of your system running as easily as possible and making sure that external systems do not change your production environment, e.g. provide a separate test database with (anonymized) data from a production system or being able to setup a new schema with a basic dataset for your test environment. Since these tests do not rely as much on isolating testable code and instead look at the interconnected system, writing them is usually easier and more productive when doing a migration. You can then limit your effort on writing lower level tests on parts of the code that you have to change or replace in the new application making sure it is testable right from the start.

各クラスが期待どおりに機能することを保証する低レベルのテストを提供する代わりに、少なくとも表面的なレベルでユーザーが直面するすべてのものが機能することを保証する高レベルのテストを作成することは理にかなっています。これらの種類のテストは、一般にエンド ツー エンド テストと呼ばれます。これは、ユーザーがブラウザーで見るものから、実行中のコードやデータベースのような接続されたサービスまで、アプリケーション全体をカバーするためです。これを自動化するには、システムのテストインスタンスをできるだけ簡単に実行できるようにし、外部システムが本番環境を変更しないようにする必要があります。たとえば、本番システムまたはテスト環境用の基本的なデータセットを使用して新しいスキーマをセットアップできます。これらのテストは、テスト可能なコードの分離にあまり依存せず、代わりに相互接続されたシステムを調べるため、通常、移行を行う際にはそれらを記述する方が簡単で生産的です。これにより、新しいアプリケーションで変更または置換する必要があるコードの部分で低レベルのテストを作成する労力を制限して、最初からテスト可能であることを確認できます。

There are tools aimed at End-to-End testing you can use such as Symfony Panther or you can write functional tests in the new Symfony application as soon as the initial setup is completed. For example you can add so called Smoke Tests, which only ensure a certain path is accessible by checking the HTTP status code returned or looking for a text snippet from the page.

Symfony Panther など、使用できるエンドツーエンド テストを目的としたツールがあります。または、初期セットアップが完了するとすぐに、新しい Symfony アプリケーションで機能テストを作成できます。たとえば、いわゆるスモーク テストを追加できます。返された HTTP ステータス コードを確認するか、ページからテキスト スニペットを探すことで、特定のパスにアクセスできます。

Introducing Symfony to the Existing Application

The following instructions only provide an outline of common tasks for setting up a Symfony application that falls back to a legacy application whenever a route is not accessible. Your mileage may vary and likely you will need to adjust some of this or even provide additional configuration or retrofitting to make it work with your application. This guide is not supposed to be comprehensive and instead aims to be a starting point.

次の手順は、ルートにアクセスできない場合は常にレガシー アプリケーションにフォールバックする Symfony アプリケーションをセットアップするための一般的なタスクの概要のみを提供します。走行距離は異なる場合があり、アプリケーションで動作するように、これの一部を調整するか、追加の構成または改良を提供する必要がある可能性があります.このガイドは包括的なものではなく、出発点となることを目的としています。

Tip

ヒント

If you get stuck or need additional help you can reach out to the Symfony community whenever you need concrete feedback on an issue you are facing.

行き詰まったり、追加のヘルプが必要な場合は、直面している問題について具体的なフィードバックが必要なときにいつでも Symfony コミュニティに連絡できます。

Booting Symfony in a Front Controller

When looking at how a typical PHP application is bootstrapped there are two major approaches. Nowadays most frameworks provide a so called front controller which acts as an entrypoint. No matter which URL-path in your application you are going to, every request is being sent to this front controller, which then determines which parts of your application to load, e.g. which controller and action to call. This is also the approach that Symfony takes with public/index.php being the front controller. Especially in older applications it was common that different paths were handled by different PHP files.

典型的な PHP アプリケーションがどのようにブートストラップされるかを見ると、2 つの主要なアプローチがあります。現在、ほとんどのフレームワークは、エントリポイントとして機能する、いわゆるフロント コントローラーを提供しています。アプリケーションのどの URL パスにアクセスしようとしても、すべてのリクエストはこのフロント コントローラーに送信されます。呼び出すコントローラーとアクション。これは、Symfony が public/index.php をフロント コントローラーとして採用するアプローチでもあります。特に古いアプリケーションでは、異なるパスが異なる PHP ファイルによって処理されることが一般的でした。

In any case you have to create a public/index.php that will start your Symfony application by either copying the file from the FrameworkBundle-recipe or by using Flex and requiring the FrameworkBundle. You will also likely have to update your web server (e.g. Apache or nginx) to always use this front controller. You can look at Web Server Configuration for examples on how this might look. For example when using Apache you can use Rewrite Rules to ensure PHP files are ignored and instead only index.php is called:

いずれにせよ、FrameworkBundle レシピからファイルをコピーするか、Flex を使用して FrameworkBundle を要求することにより、Symfony アプリケーションを開始する public/index.php を作成する必要があります。また、このフロント コントローラーを常に使用するには、Web サーバー (Apache や nginx など) を更新する必要があります。これがどのように見えるかの例については、Web サーバー構成を参照してください。たとえば、Apache を使用している場合、Rewrite Rules を使用して PHP ファイルを無視し、代わりに index.php のみを呼び出すことができます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
RewriteEngine On

RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
RewriteRule ^(.*) - [E=BASE:%1]

RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]

RewriteRule ^index\.php - [L]

RewriteCond %{REQUEST_FILENAME} -f
RewriteCond %{REQUEST_FILENAME} !^.+\.php$
RewriteRule ^ - [L]

RewriteRule ^ %{ENV:BASE}/index.php [L]

This change will make sure that from now on your Symfony application is the first one handling all requests. The next step is to make sure that your existing application is started and taking over whenever Symfony can not yet handle a path previously managed by the existing application.

この変更により、Symfony アプリケーションがすべてのリクエストを最初に処理するようになります。次のステップは、Symfony が既存のアプリケーションによって以前に管理されていたパスをまだ処理できない場合に、既存のアプリケーションが開始され、引き継がれるようにすることです。

From this point, many tactics are possible and every project requires its unique approach for migration. This guide shows two examples of commonly used approaches, which you can use as a base for your own approach:

この時点から、多くの戦術が可能であり、すべてのプロジェクトには移行のための独自のアプローチが必要です。このガイドでは、一般的に使用されるアプローチの 2 つの例を示します。これらは、独自のアプローチのベースとして使用できます。
  • Front Controller with Legacy Bridge, which leaves the legacy application untouched and allows migrating it in phases to the Symfony application.
    レガシー ブリッジを備えたフロント コントローラー。これにより、レガシー アプリケーションは変更されず、段階的に Symfony アプリケーションに移行できます。
  • Legacy Route Loader, where the legacy application is integrated in phases into Symfony, with a fully integrated final result.
    レガシー アプリケーションが段階的に Symfony に統合され、完全に統合された最終結果が得られるレガシー ルート ローダー。

Front Controller with Legacy Bridge

Once you have a running Symfony application that takes over all requests, falling back to your legacy application is done by extending the original front controller script with some logic for going to your legacy system. The file could look something like this:

すべてのリクエストを引き継ぐ実行中の Symfony アプリケーションを取得したら、レガシー システムに移動するためのロジックを使用して元の frontcontroller スクリプトを拡張することにより、レガシー アプリケーションにフォールバックします。ファイルは次のようになります。
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// public/index.php
use App\Kernel;
use App\LegacyBridge;
use Symfony\Component\Dotenv\Dotenv;
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\HttpFoundation\Request;

require dirname(__DIR__).'/vendor/autoload.php';

(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');

/*
 * The kernel will always be available globally, allowing you to
 * access it from your existing application and through it the
 * service container. This allows for introducing new features in
 * the existing application.
 */
global $kernel;

if ($_SERVER['APP_DEBUG']) {
    umask(0000);

    Debug::enable();
}

if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? $_ENV['TRUSTED_PROXIES'] ?? false) {
    Request::setTrustedProxies(
      explode(',', $trustedProxies),
      Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO
    );
}

if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? $_ENV['TRUSTED_HOSTS'] ?? false) {
    Request::setTrustedHosts([$trustedHosts]);
}

$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);

if (false === $response->isNotFound()) {
    // Symfony successfully handled the route.
    $response->send();
} else {
    LegacyBridge::handleRequest($request, $response, __DIR__);
}

$kernel->terminate($request, $response);

There are 2 major deviations from the original file:

元のファイルから 2 つの大きな相違点があります。
Line 18
First of all, $kernel is made globally available. This allows you to use Symfony features inside your existing application and gives access to services configured in our Symfony application. This helps you prepare your own code to work better within the Symfony application before you transition it over. For instance, by replacing outdated or redundant libraries with Symfony components.
まず、$kernel がグローバルに利用可能になります。これにより、既存のアプリケーション内で Symfony の機能を使用できるようになり、Symfony アプリケーションで構成されたサービスにアクセスできるようになります。これは、Symfony アプリケーションを移行する前に、Symfony アプリケーション内でより適切に動作するように独自のコードを準備するのに役立ちます。たとえば、古いライブラリや冗長なライブラリを Symfony コンポーネントに置き換えることによって。
Line 41 - 46
If Symfony handled the response, it is sent; otherwise, the LegacyBridge handles the request.
Symfony が応答を処理した場合、それは送信されます。それ以外の場合、LegacyBridge が要求を処理します。

This legacy bridge is responsible for figuring out which file should be loaded in order to process the old application logic. This can either be a front controller similar to Symfony's public/index.php or a specific script file based on the current route. The basic outline of this LegacyBridge could look somewhat like this:

このレガシー ブリッジは、古いアプリケーション ロジックを処理するためにどのファイルをロードする必要があるかを判断します。これは、Symfony の public/index.php に似たフロントコントローラーか、現在のルートに基づく特定のスクリプト ファイルのいずれかです。この LegacyBridge の基本的な概要は、次のようになります。
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// src/LegacyBridge.php
namespace App;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class LegacyBridge
{

    /**
     * Map the incoming request to the right file. This is the
     * key function of the LegacyBridge.
     *
     * Sample code only. Your implementation will vary, depending on the
     * architecture of the legacy code and how it's executed.
     *
     * If your mapping is complicated, you may want to write unit tests
     * to verify your logic, hence this is public static.
     */
    public static function getLegacyScript(Request $request): string
    {
        $requestPathInfo = $request->getPathInfo();
        $legacyRoot = __DIR__ . '/../';

        // Map a route to a legacy script:
        if ($requestPathInfo == '/customer/') {
            return "{$legacyRoot}src/customers/list.php";
        }

        // Map a direct file call, e.g. an ajax call:
        if ($requestPathInfo == 'inc/ajax_cust_details.php') {
            return "{$legacyRoot}inc/ajax_cust_details.php";
        }

        // ... etc.

        throw new \Exception("Unhandled legacy mapping for $requestPathInfo");
    }


    public static function handleRequest(Request $request, Response $response, string $publicDirectory): ?string
    {
        $legacyScriptFilename = LegacyBridge::getLegacyScript($request);

        // Possibly (re-)set some env vars (e.g. to handle forms
        // posting to PHP_SELF):
        $p = $request->getPathInfo();
        $_SERVER['PHP_SELF'] = $p;
        $_SERVER['SCRIPT_NAME'] = $p;
        $_SERVER['SCRIPT_FILENAME'] = $legacyScriptFilename;

        require $legacyScriptFilename;
    }
}

This is the most generic approach you can take, that is likely to work no matter what your previous system was. You might have to account for certain "quirks", but since your original application is only started after Symfony finished handling the request you reduced the chances for side effects and any interference.

これは、使用できる最も一般的なアプローチであり、以前のシステムが何であったとしても機能する可能性があります。特定の「癖」を考慮する必要があるかもしれませんが、元のアプリケーションは Symfony がリクエストの処理を終了した後にのみ開始されるため、副作用や干渉の可能性を減らしました。

Since the old script is called in the global variable scope it will reduce side effects on the old code which can sometimes require variables from the global scope. At the same time, because your Symfony application will always be booted first, you can access the container via the $kernel variable and then fetch any service (using getContainer()). This can be helpful if you want to introduce new features to your legacy application, without switching over the whole action to the new application. For example, you could now use the Symfony Translator in your old application or instead of using your old database logic, you could use Doctrine to refactor old queries. This will also allow you to incrementally improve the legacy code making it easier to transition it over to the new Symfony application.

古いスクリプトはグローバル変数スコープで呼び出されるため、グローバルスコープからの変数を必要とする場合がある古いコードの副作用が軽減されます。同時に、Symfony アプリケーションは常に最初に起動されるため、$kernel 変数を介してコンテナーにアクセスし、(getContainer() を使用して) サービスを取得できます。これは、レガシーアプリケーションに新しい機能を導入したい場合に役立ちます。アクション全体を新しいアプリケーションに切り替える必要はありません。たとえば、古いアプリケーションで Symfony Translator を使用したり、古いデータベース ロジックを使用する代わりに、Doctrine を使用して古いクエリをリファクタリングしたりできます。これにより、従来のコードを段階的に改善して、新しい Symfony アプリケーションへの移行を容易にすることもできます。

The major downside is, that both systems are not well integrated into each other leading to some redundancies and possibly duplicated code. For example, since the Symfony application is already done handling the request you can not take advantage of kernel events or utilize Symfony's routing for determining which legacy script to call.

主な欠点は、両方のシステムが相互にうまく統合されていないことです。これにより、冗長性が生じ、コードが重複する可能性があります。たとえば、Symfony アプリケーションはすでにリクエストの処理を完了しているため、カーネル イベントを利用したり、Symfony のルーティングを使用して判断したりすることはできません。どのレガシー スクリプトを呼び出すか。

Legacy Route Loader

The major difference to the LegacyBridge-approach from before is, that the logic is moved inside the Symfony application. It removes some of the redundancies and allows us to also interact with parts of the legacy application from inside Symfony, instead of just the other way around.

以前との LegacyBridge アプローチとの主な違いは、ロジックが Symfony アプリケーション内に移動されたことです。これにより冗長性の一部が取り除かれ、Symfony 内からレガシーアプリケーションの一部とやり取りできるようになります。その逆ではありません。

Tip

ヒント

The following route loader is just a generic example that you might have to tweak for your legacy application. You can familiarize yourself with the concepts by reading up on it in Routing.

次のルート ローダーは、レガシー アプリケーション用に調整する必要がある一般的な例です。ルーティングでそれを読むことで、概念に慣れることができます。

The legacy route loader is a custom route loader. The legacy route loader has a similar functionality as the previous LegacyBridge, but it is a service that is registered inside Symfony's Routing component:

レガシー ルート ローダーはカスタム ルート ローダーです。レガシー ルート ローダーには、以前のLegacyBridge と同様の機能がありますが、Symfony の Routing コンポーネント内に登録されているサービスです。
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
29
30
31
32
33
// src/Legacy/LegacyRouteLoader.php
namespace App\Legacy;

use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

class LegacyRouteLoader extends Loader
{
    // ...

    public function load($resource, $type = null)
    {
        $collection = new RouteCollection();
        $finder = new Finder();
        $finder->files()->name('*.php');

        /** @var SplFileInfo $legacyScriptFile */
        foreach ($finder->in($this->webDir) as $legacyScriptFile) {
            // This assumes all legacy files use ".php" as extension
            $filename = basename($legacyScriptFile->getRelativePathname(), '.php');
            $routeName = sprintf('app.legacy.%s', str_replace('/', '__', $filename));

            $collection->add($routeName, new Route($legacyScriptFile->getRelativePathname(), [
                '_controller' => 'App\Controller\LegacyController::loadLegacyScript',
                'requestPath' => '/' . $legacyScriptFile->getRelativePathname(),
                'legacyScript' => $legacyScriptFile->getPathname(),
            ]));
        }

        return $collection;
    }
}

You will also have to register the loader in your application's routing.yaml as described in the documentation for Custom Route Loaders. Depending on your configuration, you might also have to tag the service with routing.loader. Afterwards you should be able to see all the legacy routes in your route configuration, e.g. when you call the debug:router-command:

また、カスタム ルート ローダーのドキュメントで説明されているように、アプリケーションのrouting.yaml にローダーを登録する必要があります。その後、ルート構成ですべてのレガシールートを確認できるはずです。 debug:router-command を呼び出す場合:
1
$ php bin/console debug:router

In order to use these routes you will need to create a controller that handles these routes. You might have noticed the _controller attribute in the previous code example, which tells Symfony which Controller to call whenever it tries to access one of our legacy routes. The controller itself can then use the other route attributes (i.e. requestPath and legacyScript) to determine which script to call and wrap the output in a response class:

これらのルートを使用するには、これらのルートを処理するコントローラーを作成する必要があります。前のコード例の _controller 属性に気づいたかもしれません。これは、レガシールートの 1 つにアクセスしようとするたびにどのコントローラーを呼び出すかを Symfony に指示します。コントローラー自体は、他のルート属性 (つまり、requestPath と legacyScript) を使用して、呼び出すスクリプトを決定し、出力を応答クラスにラップできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/Controller/LegacyController.php
namespace App\Controller;

use Symfony\Component\HttpFoundation\StreamedResponse;

class LegacyController
{
    public function loadLegacyScript(string $requestPath, string $legacyScript)
    {
        return new StreamedResponse(
            function () use ($requestPath, $legacyScript) {
                $_SERVER['PHP_SELF'] = $requestPath;
                $_SERVER['SCRIPT_NAME'] = $requestPath;
                $_SERVER['SCRIPT_FILENAME'] = $legacyScript;

                chdir(dirname($legacyScript));

                require $legacyScript;
            }
        );
    }
}

This controller will set some server variables that might be needed by the legacy application. This will simulate the legacy script being called directly, in case it relies on these variables (e.g. when determining relative paths or file names). Finally the action requires the old script, which essentially calls the original script as before, but it runs inside our current application scope, instead of the global scope.

このコントローラーは、レガシー アプリケーションで必要になる可能性のあるいくつかのサーバー変数を設定します。これにより、これらの変数に依存する場合 (相対パスやファイル名を決定する場合など) に、直接呼び出されるレガシー スクリプトがシミュレートされます。最後に、アクションには古いスクリプトが必要です。これは基本的に以前と同じように元のスクリプトを呼び出しますが、グローバル スコープではなく、現在のアプリケーション スコープ内で実行されます。

There are some risks to this approach, as it is no longer run in the global scope. However, since the legacy code now runs inside a controller action, you gain access to many functionalities from the new Symfony application, including the chance to use Symfony's event lifecycle. For instance, this allows you to transition the authentication and authorization of the legacy application over to the Symfony application using the Security component and its firewalls.

グローバルスコープで実行されなくなったため、このアプローチにはいくつかのリスクがあります。ただし、レガシー コードはコントローラー アクション内で実行されるようになったため、Symfony のイベント ライフサイクルを使用する機会を含め、新しい Symfony アプリケーションから多くの機能にアクセスできます。たとえば、これにより、セキュリティ コンポーネントとそのファイアウォールを使用して、レガシー アプリケーションの認証と承認を Symfony アプリケーションに移行できます。