海外社媒SNS代运营Tiktok代运营公司
Revive Old Posts

营销型WordPress外贸多语言独立站建设+谷歌SEO优化+谷歌关键字广告投放,就找WP花园!深圳公司,技术实力雄厚,用效果说话!详情咨询info@wordpresshy.com

WordPress 是一个古老的 CMS,但也是最常用的。 由于其支持过时的 PHP 版本和遗留代码的历史,它仍然缺乏实现现代编码实践——WordPress 抽象就是一个例子。

例如,将 WordPress 核心代码库拆分为 Composer 管理的包会好得多。 或者,从文件路径自动加载 WordPress 类。

本文将教你如何手动抽象 WordPress 代码并使用抽象的 WordPress 插件功能。

集成 WordPress 和 PHP 工具的问题

由于其古老的架构,我们在将 WordPress 与 PHP 代码库工具集成时偶尔会遇到问题,例如静态分析器 PHPStan、单元测试库 PHPUnit 和命名空间范围库 PHP-Scoper。 例如,考虑以下情况:

我们项目中的 WordPress 代码只是总数的一小部分; 该项目还将包含与底层 CMS 无关的业务代码。 然而,仅仅通过一些 WordPress 代码,该项目可能无法正确地与工具集成。

正因为如此,将项目拆分为多个包可能是有意义的,其中一些包含 WordPress 代码,而另一些只包含使用“vanilla”PHP 的业务代码而没有 WordPress 代码。 这样,后面的这些包不会受到上述问题的影响,但可以与工具完美集成。

什么是代码抽象?

代码抽象从代码中删除了固定的依赖关系,生成通过契约相互交互的包。 然后可以将这些包添加到具有不同堆栈的不同应用程序中,从而最大限度地提高它们的可用性。 代码抽象的结果是一个基于以下支柱的完全解耦的代码库:

海外社媒SNS代运营Tiktok代运营公司
  1. 针对接口的代码,而不是实现。
  2. 创建包并通过 Composer 分发它们。
  3. 通过依赖注入将所有部分粘合在一起。

想了解更多关于 WordPress 代码抽象的信息吗? 👩‍💻 从最佳实践到推荐插件,您需要知道的一切只需点击一下⬇️点击推文

针对接口而不是实现编码

针对接口编码是使用契约让代码片段相互交互的做法。 合约只是一个 PHP 接口(或任何不同的语言),它定义了哪些函数可用及其签名,即它们接收哪些输入和输出。

接口声明了功能的意图,而不解释功能将如何实现。 通过接口访问功能,我们的应用程序可以依赖自主代码段来完成特定目标,而无需知道或关心它们是如何实现的。 通过这种方式,应用程序无需进行调整以切换到实现相同目标的另一段代码——例如,来自不同的提供者。

合同示例

以下代码使用 Symfony 的合约 CacheInterface 和 PHP 标准推荐 (PSR) 合同 CacheItemInterface 实现缓存功能:

use PsrCacheCacheItemInterface;
use SymfonyContractsCacheCacheInterface;

$value = $cache->get('my_cache_key', function (CacheItemInterface $item) {
    $item->expiresAfter(3600);
    return 'foobar';
});

$cache 工具 CacheInterface,它定义了方法 get 从缓存中检索对象。 通过合约访问此功能,应用程序可以不知道缓存在哪里。 无论是在内存、磁盘、数据库、网络还是其他任何地方。 尽管如此,它必须执行该功能。 CacheItemInterface 定义方法 expiresAfter 声明项目必须在缓存中保留多长时间。 应用程序可以调用这个方法而不用关心缓存的对象是什么; 它只关心必须缓存多长时间。

针对 WordPress 中的接口编码

因为我们正在抽象 WordPress 代码,结果将是应用程序不会直接引用 WordPress 代码,而是始终通过接口引用。 例如,WordPress 功能 get_posts 有这个签名:

/**
 * @param array $args
 * @return WP_Post[]|int[] Array of post objects or post IDs.
 */
function get_posts( $args = null )

我们可以通过合约访问,而不是直接调用这个方法 OwnerMyAppContractsPostsAPIInterface

namespace OwnerMyAppContracts;

interface PostAPIInterface
{
  public function get_posts(array $args = null): PostInterface[]|int[];
}

注意WordPress功能 get_posts 可以返回类的对象 WP_Post,这是特定于 WordPress 的。 在抽象代码的时候,我们需要去掉这种固定的依赖。 方法 get_posts 在合约中返回类型的对象 PostInterface, 允许您引用类 WP_Post 没有明确说明。 班上 PostInterface 将需要提供对所有方法和属性的访问 WP_Post

namespace OwnerMyAppContracts;

interface PostInterface
{
  public function get_ID(): int;
  public function get_post_author(): string;
  public function get_post_date(): string;
  // ...
}

执行此策略可以改变我们对 WordPress 适合我们堆栈的位置的理解。 与其将 WordPress 视为应用程序本身(我们在其上安装主题和插件),我们可以将其简单地视为应用程序中的另一个依赖项,可以像任何其他组件一样替换。 (尽管我们不会在实践中取代 WordPress,但它 从概念的角度来看是可替换的。)

创建和分发包

Composer 是 PHP 的包管理器。 它允许 PHP 应用程序从存储库中检索包(即代码段)并将它们安装为依赖项。 要将应用程序与 WordPress 分离,我们必须将其代码分发到两种不同类型的包中:一种包含 WordPress 代码,另一种包含业务逻辑(即没有 WordPress 代码)。

最后,我们将所有包作为依赖项添加到应用程序中,并通过 Composer 安装它们。 由于工具将应用于业务代码包,因此它们必须包含应用程序的大部分代码; 百分比越高越好。 让他们管理大约 90% 的整体代码是一个很好的目标。

将 WordPress 代码提取到包中

按照前面的例子,合同 PostAPIInterfacePostInterface 将添加到包含业务代码的包中,另一个包将包含这些合同的 WordPress 实现。 为了满足 PostInterface,我们创建一个 PostWrapper 将从中检索所有属性的类 WP_Post 目的:

namespace OwnerMyAppForWPContractImplementations;

use OwnerMyAppContractsPostInterface;
use WP_Post;

class PostWrapper implements PostInterface
{
  private WP_Post $post;
  
  public function __construct(WP_Post $post)
  {
    $this->post = $post;
  }

  public function get_ID(): int
  {
    return $this->post->ID;
  }

  public function get_post_author(): string
  {
    return $this->post->post_author;
  }

  public function get_post_date(): string
  {
    return $this->post->post_date;
  }

  // ...
}

实施时 PostAPI, 由于方法 get_posts 返回 PostInterface[],我们必须将对象从 WP_PostPostWrapper

namespace OwnerMyAppForWPContractImplementations;

use OwnerMyAppContractsPostAPIInterface;
use WP_Post;

class PostAPI implements PostAPIInterface
{
  public function get_posts(array $args = null): PostInterface[]|int[]
  {
    // This var will contain WP_Post[] or int[]
    $wpPosts = get_posts($args);

    // Convert WP_Post[] to PostWrapper[]
    return array_map(
      function (WP_Post|int $post) {
        if ($post instanceof WP_Post) {
          return new PostWrapper($post);
        }
        return $post
      },
      $wpPosts
    );
  }
}

使用依赖注入

依赖注入是一种设计模式,可以让您以松散耦合的方式将所有应用程序部分粘合在一起。 通过依赖注入,应用程序通过它们的合约访问服务,合约实现通过配置“注入”到应用程序中。

WordPress花园建议你也读一下这篇文章  如何在 WordPress 中使用清漆

只需更改配置,我们就可以轻松地从一个合约提供者切换到另一个。 我们可以选择几个依赖注入库。 我们建议选择符合 PHP 标准建议(通常称为“PSR”)的库,以便在需要时可以轻松地用另一个库替换该库。 关于依赖注入,库必须满足 PSR-11,它提供了“容器接口”的规范。 其中,以下库符合 PSR-11:

通过服务容器访问服务

依赖注入库将提供一个“服务容器”,它将契约解析为其相应的实现类。 应用程序必须依赖服务容器来访问所有功能。 例如,虽然我们通常会直接调用 WordPress 函数:

$posts = get_posts();

…有了服务容器,首先要获得满足的服务 PostAPIInterface 并通过它执行功能:

use OwnerMyAppContractsPostAPIInterface;

// Obtain the service container, as specified by the library we use
$serviceContainer = ContainerBuilderFactory::getInstance();

// The obtained service will be of class OwnerMyAppForWPContractImplementationsPostAPI
$postAPI = $serviceContainer->get(PostAPIInterface::class);

// Now we can invoke the WordPress functionality
$posts = $postAPI->get_posts();

使用 Symfony 的依赖注入

Symfony 的 DependencyInjection 组件是目前最流行的依赖注入库。 它允许您通过 PHP、YAML 或 XML 代码配置服务容器。 例如,要定义该合同 PostAPIInterface 通过课堂感到满意 PostAPI 在 YAML 中配置如下:

services:
  OwnerMyAppContractsPostAPIInterface:
    class: OwnerMyAppForWPContractImplementationsPostAPI

Symfony 的 DependencyInjection 还允许将来自一个服务的实例自动注入(或“自动装配”)到依赖它的任何其他服务中。 此外,它可以很容易地定义一个类是它自己的服务的实现。 例如,考虑以下 YAML 配置:

services:
  _defaults:
    public: true
    autowire: true

  GraphQLAPIGraphQLAPIRegistriesUserAuthorizationSchemeRegistryInterface:
    class: 'GraphQLAPIGraphQLAPIRegistriesUserAuthorizationSchemeRegistry'

  GraphQLAPIGraphQLAPISecurityUserAuthorizationInterface:
    class: 'GraphQLAPIGraphQLAPISecurityUserAuthorization'
    
  GraphQLAPIGraphQLAPISecurityUserAuthorizationSchemes:
    resource: '../src/Security/UserAuthorizationSchemes/*'

此配置定义了以下内容:

  • 合同 UserAuthorizationSchemeRegistryInterface 通过课堂感到满意 UserAuthorizationSchemeRegistry
  • 合同 UserAuthorizationInterface 通过课堂感到满意 UserAuthorization
  • 文件夹下的所有类 UserAuthorizationSchemes/ 是他们自己的实现
  • 服务必须自动相互注入(autowire: true)

让我们看看自动装配是如何工作的。 班上 UserAuthorization 取决于合同服务 UserAuthorizationSchemeRegistryInterface

class UserAuthorization implements UserAuthorizationInterface
{
  public function __construct(
      protected UserAuthorizationSchemeRegistryInterface $userAuthorizationSchemeRegistry
  ) {
  }

  // ...
}

谢谢 autowire: true,DependencyInjection 组件会自动拥有服务 UserAuthorization 接收其所需的依赖项,这是一个实例 UserAuthorizationSchemeRegistry.

何时抽象

抽象代码会消耗大量的时间和精力,所以我们应该只在它的收益大于成本的情况下进行它。 以下是关于何时抽象代码可能值得的建议。 您可以使用本文中的代码片段或下面建议的抽象 WordPress 插件来完成此操作。

获得对工具的访问

如前所述,在 WordPress 上运行 PHP-Scoper 很困难。 通过将 WordPress 代码解耦到不同的包中,直接确定 WordPress 插件的范围变得可行。

减少加工时间和成本

运行 PHPUnit 测试套件需要更长的时间…

营销型WordPress外贸多语言独立站建设+谷歌SEO优化+谷歌关键字广告投放,就找WP花园!深圳公司,技术实力雄厚,用效果说话!详情咨询info@wordpresshy.com

海外社媒SNS代运营Tiktok代运营公司
Revive Old Posts