营销型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 代码。 这样,后面的这些包不会受到上述问题的影响,但可以与工具完美集成。
什么是代码抽象?
代码抽象从代码中删除了固定的依赖关系,生成通过契约相互交互的包。 然后可以将这些包添加到具有不同堆栈的不同应用程序中,从而最大限度地提高它们的可用性。 代码抽象的结果是一个基于以下支柱的完全解耦的代码库:
- 针对接口的代码,而不是实现。
- 创建包并通过 Composer 分发它们。
- 通过依赖注入将所有部分粘合在一起。
针对接口而不是实现编码
针对接口编码是使用契约让代码片段相互交互的做法。 合约只是一个 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 代码提取到包中
按照前面的例子,合同 PostAPIInterface
和 PostInterface
将添加到包含业务代码的包中,另一个包将包含这些合同的 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_Post
到 PostWrapper
:
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
);
}
}
使用依赖注入
依赖注入是一种设计模式,可以让您以松散耦合的方式将所有应用程序部分粘合在一起。 通过依赖注入,应用程序通过它们的合约访问服务,合约实现通过配置“注入”到应用程序中。
只需更改配置,我们就可以轻松地从一个合约提供者切换到另一个。 我们可以选择几个依赖注入库。 我们建议选择符合 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