webfactory/legacy-integration-bundle

一种经过实战验证的方法,用于简化将旧应用程序逐步迁移到Symfony2堆栈的过程。

2.3.0 2024-03-14 15:55 UTC

README

此包提供了一种将现有旧应用程序与Symfony2集成的方案。

简而言之,该方案是在kernel.controller事件上运行特定的事件监听器。此事件监听器将决定是否以及如何运行旧应用程序。随后将捕获旧应用程序生成的响应。

随后,在Symfony2控制器执行并渲染Twig模板后,可以检索“旧响应”的片段并将其用作输出的一部分。

这样,您可以从旧应用程序和Symfony2共存开始。然后可以逐步将功能转移到Symfony2堆栈,同时保持连贯的用户体验。

由于Symfony2推动这一过程,而Twig模板拥有控制权,决定哪些部分旧响应被使用,因此此方法自然会“成长”并远离旧应用程序,向Symfony2堆栈发展。

请注意:此包当前使用webfactory/dom,它是在PHP的DOM扩展之上的一个小型实用工具集,用于处理旧应用程序的响应并提供基于XPath的访问。它仅适用于您的旧应用程序返回格式良好的XHTML或Polyglot HTML5。

要了解更多关于此概念、其优势和注意事项,请使用您的时光机并访问2013年12月18日Symfony2用户组Colone会议

安装

与其他任何Symfony2包一样,留给读者作为练习。

配置

webfactory_legacy_integration:
    # Whether your legacy application returns text/html as XHTML 1.0 or Polyglot HTML5.
    parsingMode: xhtml10|html5
    # Bootstrap file for the legacy application (see next section)
    legacyApplicationBootstrapFile: www/index-legacy.php

旧的“引导”文件

您必须提供一个可以include()的单个文件,以便运行您的旧应用程序。通常您已经拥有它 - 这应该是您的旧应用程序的前端控制器。

如果您正在运行PHP < 5.4,此文件应返回旧应用程序发送的HTTP状态代码。从PHP 5.4开始,将使用http_response_code()来检测它。

此外,由于此包将尝试使用输出缓冲捕获包括头在内的响应,因此您不得将响应体或头发送到客户端。

使用方法

一旦设置好,使用控制器上的@Dispatch注解在控制器之前运行旧应用程序,如下所示

<?php
namespace Acme\My\Bundle\Controller;

use Webfactory\Bundle\LegacyIntegrationBundle\Integration\Annotation as Legacy;
use ...

class MyController ...
{
    /**
     * @Legacy\Dispatch
     */
    public function myAction()
    {
        return ...
    }
}

这将在进入控制器之前运行旧应用程序。整个输出包括HTTP头(重复:包括HTTP头)将被捕获并保存。除非您处理,否则不会向客户端发送任何内容。

关于旧响应体,有两种方法将旧世界与新世界混合:要么创建一个新的布局并嵌入旧应用程序的部分,要么保留旧的布局并嵌入其中新部分。以下各节将解释这两种方法。

关于HTTP头和旧应用程序发送的cookie,请确保不要错过下面解释的过滤器。例如,如果您的旧代码使用session_start(),您可能需要转发会话cookie。

使用XPath将旧响应的部分嵌入新布局中

这是我们通常推荐的方法。如果您正在进行前端重设计的历史集成,或者如果将布局重写为Symfony世界中的布局不是太难,您应该这样做。

只有使用这种方法,您才可以在不运行旧应用程序的情况下在Symfony 2堆栈上实现新功能。

对于需要在旧系统中(部分)运行的使用案例,您可以使用由包提供的Twig全局legacyApplication来访问响应的部分并将它们放在您的视图中。

{# you_template.html.twig #}
<html>
    ...
    <body>
        ...
        <div id="content">
            {{ legacyApplication.xpath('//*[@id="content"]/*') | raw }}
        </div>
        ...
    </body>
</html>

用由Twig生成的标记替换旧响应中的任意占位符

有时您必须专注于在Symfony 2堆栈上提供新功能,而您将陷入由旧应用程序生成的页面布局。

这的缺点是每次请求都必须运行旧应用程序才能获取此布局,这在性能上是一个惩罚。即使是完全新的功能也需要每次都运行旧应用程序。

两个Twig函数webfactoy_legacy_integration_embed()webfactory_legacy_integration_embed_result()有助于这种集成方式。

  • webfactory_legacy_integration_embed(placeholder, replacement)将在旧应用程序的响应中搜索任意的placeholder字符串。然后它将它们替换为replacement。此函数可用于多次不同的替换。

  • webfactory_legacy_integration_embed_result()将在执行一个或多个替换后返回最终结果。

这些替换最好保存在Twig的基础布局模板中,并映射到Twig块,如下所示

{# baseLayout.html.twig #}
{{ webfactory_legacy_integration_embed('<!-- MAIN_CONTENT -->', block('main_content')) }}
{{ webfactory_legacy_integration_embed_result() }}

此布局模板除了由webfactory_legacy_integration_embed_result()提供的输出之外不执行任何其他输出。然后它可以这样扩展

{# new-functionality.html.twig #}
{% extends 'baseLayout.html.twig' %}
{% block main_content %}
    your new content here
{% end block %}

这的好处是使new-functionality.html.twig模板与其与旧应用程序的集成方式隔离开。一旦将页面布局转移到Symfony 2堆栈上,就可以更新baseLayout.html.twig模板(当然它应该输出main_content块!)并停止为新功能分发旧应用程序。

过滤器

LegacyApplicationDispatchingEventListener可以接受一组必须实现Webfactory\Bundle\LegacyIntegrationBundle\Integration\Filter接口的过滤器

一旦执行了旧应用程序,所有注册的过滤器都会传递给触发事件监听器的ControllerEvent以及为旧应用程序创建的Response对象。

此用例的主要用途是能够检查响应并选择将其原样发送给客户端,从而绕过执行Symfony2控制器。这样,您就可以在Symfony2中有路由/控制器来触发(或允许)执行旧应用程序中的特定使用案例,同时最初返回未修改的响应。

您可以使用webfactory_legacy_integration.filter标签添加更多过滤器。更方便的方法是使用以下额外的注解。

过滤器注解

Webfactory\Bundle\LegacyIntegrationBundle\Integration\Annotation命名空间包含一些您可以使用的注解,除了前面描述的@Legacy\Dispatch注解外。

特别是,

  • @Legacy\Passthru将旧应用程序的响应原样发送,因此控制器本身将永远不会运行
  • @Legacy\IgnoreRedirect如果旧应用程序发送了Location:重定向头,则绕过控制器。
  • @Legacy\IgnoreHeader("some-name")如果旧应用程序发送了"Some-Name:"头,则绕过控制器。这可以用来让旧应用程序控制Symfony2控制器的执行(谨慎使用)。
  • @Legacy\KeepHeaders将应用旧响应中找到的所有HTTP头,并将它们添加到由Symfony控制器创建的响应中。您还可以通过@Legacy\KeepHeaders({"X-Some-Header", "X-Some-Other"})选择性地选择头。
  • @Legacy\KeepCookies 的功能类似于 KeepHeaders,但会查看cookie名称。

错误

我们已成功使用此包将几个应用程序和项目逐步迁移到 Symfony2 堆栈。然而,它还需要更多的关注、细致打磨和文档。欢迎为它提交PR或打开issue,表达你对它的兴趣。

哦,单元测试也会很好 :)