如何使用PHP类扩展WordPress插件

作者 : King 本文共17253个字,预计阅读时间需要44分钟 发布时间: 2025-05-11 共5人阅读

wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions-1024x512-1

WordPress 插件可以通过附加功能进行扩展,WooCommerce 和 Gravity Forms 等流行插件就证明了这一点。在 “构建支持扩展的 WordPress 插件” 一文中,我们了解到使 WordPress 插件具有可扩展性的两种主要方法:

  1. 为扩展插件设置钩子(动作和过滤器),以便其注入自己的功能
  2. 分享扩展插件可以继承的 PHP 类

第一种方法更多地依赖于文档,详细说明可用的钩子及其用法。相比之下,第二种方法为扩展分享即用代码,减少了对大量文档的需求。这样做的好处是,在创建代码的同时创建文档,会使插件的管理和发布复杂化。

直接分享 PHP 类可以有效地用代码取代文档。插件不是教授如何实现某个功能,而是分享必要的 PHP 代码,从而简化了第三方开发人员的工作。

让我们来探讨一些实现这一目标的技巧,最终目标是围绕我们的 WordPress 插件建立一个集成生态系统。

在 WordPress 插件中定义基础 PHP 类

WordPress 插件将包含供扩展插件使用的 PHP 类。这些 PHP 类可能不会被主插件本身使用,而是专门分享给其他插件使用。

让我们看看开源的 Gato GraphQL 插件是如何实现的。

AbstractPlugin 类:

AbstractPlugin 表示一个插件,包括 Gato GraphQL 主插件及其扩展插件:

abstract class AbstractPlugin implements PluginInterface{protected string $pluginBaseName;protected string $pluginSlug;protected string $pluginName;public function __construct(protected string $pluginFile,protected string $pluginVersion,?string $pluginName,) {$this->pluginBaseName = plugin_basename($pluginFile);$this->pluginSlug = dirname($this->pluginBaseName);$this->pluginName = $pluginName ?? $this->pluginBaseName;}public function getPluginName(): string{return $this->pluginName;}public function getPluginBaseName(): string{return $this->pluginBaseName;}public function getPluginSlug(): string{return $this->pluginSlug;}public function getPluginFile(): string{return $this->pluginFile;}public function getPluginVersion(): string{return $this->pluginVersion;}public function getPluginDir(): string{return dirname($this->pluginFile);}public function getPluginURL(): string{return plugin_dir_url($this->pluginFile);}// ...}

AbstractMainPlugin 类:

AbstractMainPlugin 扩展了 AbstractPlugin 以表示主插件:

abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface{public function __construct(string $pluginFile,string $pluginVersion,?string $pluginName,protected MainPluginInitializationConfigurationInterface $pluginInitializationConfiguration,) {parent::__construct($pluginFile,$pluginVersion,$pluginName,);}// ...}

AbstractExtension 类:

同样,AbstractExtension 扩展了 AbstractPlugin 以表示扩展插件:

abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface{public function __construct(string $pluginFile,string $pluginVersion,?string $pluginName,protected ?ExtensionInitializationConfigurationInterface $extensionInitializationConfiguration,) {parent::__construct($pluginFile,$pluginVersion,$pluginName,);}// ...}

请注意,AbstractExtension  包含在主插件中,分享了注册和初始化扩展的功能。不过,它只被扩展程序使用,而不是被主插件本身使用。

AbstractPlugin 类包含在不同时间调用的共享初始化代码。这些方法是在祖先级别定义的,但继承类会根据其生命周期进行调用。

主插件和扩展通过执行相应类的 setup 方法进行初始化,该方法在 WordPress 主插件文件中调用。

例如,在 Gato GraphQL 中,这是在 gatographql.php 中完成的:

$pluginFile = __FILE__;$pluginVersion = '2.4.0';$pluginName = __('Gato GraphQL', 'gatographql');PluginApp::getMainPluginManager()->register(new Plugin($pluginFile,$pluginVersion,$pluginName))->setup();

setup 方法:

在祖先级别(ancestor level),setup  包含插件及其扩展之间的共同逻辑,例如在插件停用时取消注册它们。该方法不是最终方法;继承类可以重写该方法,以添加自己的功能:

abstract class AbstractPlugin implements PluginInterface{// ...public function setup(): void{register_deactivation_hook($this->getPluginFile(),$this->deactivate(...));}public function deactivate(): void{$this->removePluginVersion();}private function removePluginVersion(): void{$pluginVersions = get_option('gatographql-plugin-versions', []);unset($pluginVersions[$this->pluginBaseName]);update_option('gatographql-plugin-versions', $pluginVersions);}}

主插件的 setup 方法:

主插件的 setup 方法初始化应用程序的生命周期。它通过initializeconfigureComponentsconfigureboot 等方法执行主插件的功能,并为扩展触发相应的操作钩子:

abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface{public function setup(): void{parent::setup();add_action('plugins_loaded', function (): void{// 1. Initialize main plugin$this->initialize();// 2. Initialize extensionsdo_action('gatographql:initializeExtension');// 3. Configure main plugin components$this->configureComponents();// 4. Configure extension componentsdo_action('gatographql:configureExtensionComponents');// 5. Configure main plugin$this->configure();// 6. Configure extensiondo_action('gatographql:configureExtension');// 7. Boot main plugin$this->boot();// 8. Boot extensiondo_action('gatographql:bootExtension');}// ...}// ...}

扩展 setup 方法:

AbstractExtension 类在相应的钩子上执行其逻辑:

abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface{// ...final public function setup(): void{parent::setup();add_action('plugins_loaded', function (): void{// 2. Initialize extensionsadd_action('gatographql:initializeExtension',$this->initialize(...));// 4. Configure extension componentsadd_action('gatographql:configureExtensionComponents',$this->configureComponents(...));// 6. Configure extensionadd_action('gatographql:configureExtension',$this->configure(...));// 8. Boot extensionadd_action('gatographql:bootExtension',$this->boot(...));}, 20);}}

initializeconfigureComponentsconfigure, 和 boot 方法对主插件和扩展插件都是通用的,并且知识兔可以共享逻辑。这些共享逻辑存储在 AbstractPlugin 类中。

例如,configure 方法配置插件或扩展程序,调用 callPluginInitializationConfiguration(主插件和扩展程序有不同的实现方式)和 getModuleClassConfiguration(分享默认行为,但可根据需要重载):

abstract class AbstractPlugin implements PluginInterface{// ...public function configure(): void{$this->callPluginInitializationConfiguration();$appLoader = App::getAppLoader();$appLoader->addModuleClassConfiguration($this->getModuleClassConfiguration());}abstract protected function callPluginInitializationConfiguration(): void;/*** @return array,mixed> [key]: Module class, [value]: Configuration*/public function getModuleClassConfiguration(): array{return [];}}

主插件为 callPluginInitializationConfiguration 分享其实现:

abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface{// ...protected function callPluginInitializationConfiguration(): void{$this->pluginInitializationConfiguration->initialize();}}

同样,扩展类也分享其实现:

abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface{// ...protected function callPluginInitializationConfiguration(): void{$this->extensionInitializationConfiguration?->initialize();}}

方法 initializeconfigureComponents 和 boot 定义在祖先级别,继承类可以重载这些方法:

abstract class AbstractPlugin implements PluginInterface{// ...public function initialize(): void{$moduleClasses = $this->getModuleClassesToInitialize();App::getAppLoader()->addModuleClassesToInitialize($moduleClasses);}/*** @return array> List of `Module` class to initialize*/abstract protected function getModuleClassesToInitialize(): array;public function configureComponents(): void{$classNamespace = ClassHelpers::getClassPSR4Namespace(get_called_class());$moduleClass = $classNamespace . '\\Module';App::getModule($moduleClass)->setPluginFolder(dirname($this->pluginFile));}public function boot(): void{// By default, do nothing}}

所有方法都可以被 AbstractMainPlugin 或 AbstractExtension 改写,以扩展它们的自定义功能。

对于主插件,当插件或其任何扩展被激活或停用时,setup 方法也会删除 WordPress 实例中的任何缓存:

abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface{public function setup(): void{parent::setup();// ...// Main-plugin specific methodsadd_action('activate_plugin',function (string $pluginFile): void {$this->maybeRegenerateContainerWhenPluginActivatedOrDeactivated($pluginFile);});add_action('deactivate_plugin',function (string $pluginFile): void {$this->maybeRegenerateContainerWhenPluginActivatedOrDeactivated($pluginFile);});}public function maybeRegenerateContainerWhenPluginActivatedOrDeactivated(string $pluginFile): void{// Removed code for simplicity}// ...}

同样,deactivate 方法会移除缓存,并仅在主插件 boot 时执行额外的操作钩子:

abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface{public function deactivate(): void{parent::deactivate();$this->removeTimestamps();}protected function removeTimestamps(): void{$userSettingsManager = UserSettingsManagerFacade::getInstance();$userSettingsManager->removeTimestamps();}public function boot(): void{parent::boot();add_filter('admin_body_class',function (string $classes): string {$extensions = PluginApp::getExtensionManager()->getExtensions();$commercialExtensionActivatedLicenseObjectProperties = SettingsHelpers::getCommercialExtensionActivatedLicenseObjectProperties();foreach ($extensions as $extension) {$extensionCommercialExtensionActivatedLicenseObjectProperties = $commercialExtensionActivatedLicenseObjectProperties[$extension->getPluginSlug()] ?? null;if ($extensionCommercialExtensionActivatedLicenseObjectProperties === null) {continue;}return $classes . ' is-gatographql-customer';}return $classes;});}}

从上面介绍的所有代码中,我们可以清楚地看到,在设计和编码 WordPress 插件时,我们需要考虑其扩展的需求,并尽可能在它们之间重复使用代码。实施合理的面向对象编程模式(如 SOLID 原则)有助于实现这一目标,使代码库具有长期可维护性。

声明并验证版本依赖关系

由于扩展继承自插件分享的 PHP 类,因此验证插件是否存在所需的版本至关重要。否则可能会导致冲突,导致网站瘫痪。

例如,如果知识兔 AbstractExtension 类在更新时发生了重大变化,并从以前的 3.4.0  版本发布了 4.0.0 主版本,那么在未检查版本的情况下加载扩展可能会导致 PHP 错误,从而阻止 WordPress 加载。

为避免出现这种情况,扩展必须验证所安装的插件版本为3.x.x。当安装 4.0.0 版本时,扩展将被禁用,从而避免出现错误。

扩展可以使用以下逻辑完成验证,该逻辑在扩展的主插件文件中的 plugins_loaded 钩子上执行(因为此时主插件已经加载)。该逻辑访问 ExtensionManager 类,该类包含在主插件中,知识兔用于管理扩展:

/*** Create and set-up the extension*/add_action('plugins_loaded',function (): void {/*** Extension's name and version.** Use a stability suffix as supported by Composer.*/$extensionVersion = '1.1.0';$extensionName = __('Gato GraphQL - Extension Template');/*** The minimum version required from the Gato GraphQL plugin* to activate the extension.*/$gatoGraphQLPluginVersionConstraint = '^1.0';/*** Validate Gato GraphQL is active*/if (!class_exists(\GatoGraphQL\GatoGraphQL\Plugin::class)) {add_action('admin_notices', function () use ($extensionName) {printf('

%s

',sprintf(__('Plugin %s is not installed or activated. Without it, plugin %s will not be loaded.'),__('Gato GraphQL'),$extensionName));});return;}$extensionManager = \GatoGraphQL\GatoGraphQL\PluginApp::getExtensionManager();if (!$extensionManager->assertIsValid(GatoGraphQLExtension::class,$extensionVersion,$extensionName,$gatoGraphQLPluginVersionConstraint)) {return;}// Load Composer’s autoloaderrequire_once(__DIR__ . '/vendor/autoload.php');// Create and set-up the extension instance$extensionManager->register(new GatoGraphQLExtension(__FILE__,$extensionVersion,$extensionName,))->setup();});

请注意扩展是如何声明依赖于主插件的版本约束 ^1.0 (使用Composer 的版本约束)。因此,当安装 Gato GraphQL 2.0.0版本时,扩展将不会被激活。

版本约束通过 ExtensionManager::assertIsValid 方法进行验证,该方法调用Semver::satisfies(由 composer/semver package 分享):

use Composer\Semver\Semver;class ExtensionManager extends AbstractPluginManager{/*** Validate that the required version of the Gato GraphQL for WP plugin is installed.** If the assertion fails, it prints an error on the WP admin and returns false** @param string|null $mainPluginVersionConstraint the semver version constraint required for the plugin (eg: "^1.0" means >=1.0.0 and <2.0.0)*/public function assertIsValid(string $extensionClass,string $extensionVersion,?string $extensionName = null,?string $mainPluginVersionConstraint = null,): bool {$mainPlugin = \GatoGraphQL\GatoGraphQL\PluginApp::getMainPluginManager()->getPlugin();$mainPluginVersion = $mainPlugin->getPluginVersion();if ($mainPluginVersionConstraint !== null && !Semver::satisfies($mainPluginVersion,$mainPluginVersionConstraint)) {$this->printAdminNoticeErrorMessage(sprintf(__('Extension or bundle %s requires plugin %s to satisfy version constraint %s, but the current version %s does not. The extension or bundle has not been loaded.', 'gatographql'),$extensionName ?? $extensionClass,$mainPlugin->getPluginName(),$mainPluginVersionConstraint,$mainPlugin->getPluginVersion(),));return false;}return true;}protected function printAdminNoticeErrorMessage(string $errorMessage): void{\add_action('admin_notices', function () use ($errorMessage): void {$adminNotice_safe = sprintf('

%s

',$errorMessage);echo $adminNotice_safe;});}}

针对 WordPress 服务器运行集成测试

为了让第三方开发人员更容易为您的插件创建扩展,请为他们分享开发和测试工具,包括持续集成和持续交付(CI/CD)流程的工作流。

在开发过程中,任何人都可以使用 DevKinsta 轻松启动网络服务器,安装他们为之编码扩展的插件,并立即验证扩展是否与插件兼容。

要在 CI/CD 期间自动进行测试,我们需要通过网络将网络服务器接入 CI/CD 服务。InstaWP 等服务可以为此创建一个安装了 WordPress 的沙盒网站。

如果知识兔扩展的代码库托管在 GitHub 上,开发人员可以使用 GitHub Actions 针对 InstaWP 服务运行集成测试。以下工作流程在 InstaWP 沙盒网站上安装扩展(与主插件的最新稳定版本一起),然后知识兔运行集成测试

name: Integration tests (InstaWP)on:workflow_run:workflows: [Generate plugins]types:- completedjobs:provide_data:if: ${{ github.event.workflow_run.conclusion == 'success' }}name: Retrieve the GitHub Action artifact URLs to install in InstaWPruns-on: ubuntu-lateststeps:- uses: actions/checkout@v4- uses: shivammathur/setup-php@v2with:php-version: 8.1coverage: noneenv:COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}- uses: "ramsey/composer-install@v2"- name: Retrieve artifact URLs from GitHub workflowuses: actions/github-script@v6id: artifact-urlwith:script: |const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({owner: context.repo.owner,repo: context.repo.repo,run_id: context.payload.workflow_run.id,});const artifactURLs = allArtifacts.data.artifacts.map((artifact) => {return artifact.url.replace('https://api.github.com/repos', 'https://nightly.link') + '.zip'}).concat(["https://downloads.wordpress.org/plugin/gatographql.latest-stable.zip"]);return artifactURLs.join(',');result-encoding: string- name: Artifact URL for InstaWPrun: echo "Artifact URL for InstaWP - ${{ steps.artifact-url.outputs.result }}"shell: bashoutputs:artifact_url: ${{ steps.artifact-url.outputs.result }}process:needs: provide_dataname: Launch InstaWP site from template 'integration-tests' and execute integration tests against itruns-on: ubuntu-lateststeps:- uses: actions/checkout@v4- uses: shivammathur/setup-php@v2with:php-version: 8.1coverage: noneenv:COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}- uses: "ramsey/composer-install@v2"- name: Create InstaWP instanceuses: instawp/wordpress-testing-automation@mainid: create-instawpwith:GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}INSTAWP_TOKEN: ${{ secrets.INSTAWP_TOKEN }}INSTAWP_TEMPLATE_SLUG: "integration-tests"REPO_ID: 25INSTAWP_ACTION: create-site-templateARTIFACT_URL: ${{ needs.provide_data.outputs.artifact_url }}- name: InstaWP instance URLrun: echo "InstaWP instance URL - ${{ steps.create-instawp.outputs.instawp_url }}"shell: bash- name: Extract InstaWP domainid: extract-instawp-domain run: |instawp_domain="$(echo "${{ steps.create-instawp.outputs.instawp_url }}" | sed -e s#https://##)"echo "instawp-domain=$(echo $instawp_domain)" >> $GITHUB_OUTPUT- name: Run testsrun: |INTEGRATION_TESTS_WEBSERVER_DOMAIN=${{ steps.extract-instawp-domain.outputs.instawp-domain }} \INTEGRATION_TESTS_AUTHENTICATED_ADMIN_USER_USERNAME=${{ steps.create-instawp.outputs.iwp_wp_username }} \INTEGRATION_TESTS_AUTHENTICATED_ADMIN_USER_PASSWORD=${{ steps.create-instawp.outputs.iwp_wp_password }} \vendor/bin/phpunit --filter=Integration- name: Destroy InstaWP instanceuses: instawp/wordpress-testing-automation@mainid: destroy-instawpif: ${{ always() }}with:GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}INSTAWP_TOKEN: ${{ secrets.INSTAWP_TOKEN }}INSTAWP_TEMPLATE_SLUG: "integration-tests"REPO_ID: 25INSTAWP_ACTION: destroy-site

该工作流程通过 Nightly Link 访问 .zip 文件。服务允许在不登录的情况下访问 GitHub 上的工件,从而简化了 InstaWP 的配置。

发布扩展插件

我们可以分享工具帮助发布扩展插件,尽可能实现程序自动化。

Monorepo Builder 是一个用于管理任何 PHP 项目(包括 WordPress 插件)的库。它分享了 monorepo-builder release 命令,知识兔用于发布项目版本,并根据语义版本法递增版本的 major、minor 或 patch 组件。

该命令会执行一系列发布 Worker,即执行特定逻辑的 PHP 类。默认的 Worker 包括一个创建新版本 git tag 的 Worker 和一个将标签推送到远程仓库的 Worker。自定义 Worker 可以在这些步骤之前、之后或之间注入。

发布 Worker 通过配置文件进行配置:

use Symplify\MonorepoBuilder\Config\MBConfig;use Symplify\MonorepoBuilder\Release\ReleaseWorker\AddTagToChangelogReleaseWorker;use Symplify\MonorepoBuilder\Release\ReleaseWorker\PushNextDevReleaseWorker;use Symplify\MonorepoBuilder\Release\ReleaseWorker\PushTagReleaseWorker;use Symplify\MonorepoBuilder\Release\ReleaseWorker\SetCurrentMutualDependenciesReleaseWorker;use Symplify\MonorepoBuilder\Release\ReleaseWorker\SetNextMutualDependenciesReleaseWorker;use Symplify\MonorepoBuilder\Release\ReleaseWorker\TagVersionReleaseWorker;use Symplify\MonorepoBuilder\Release\ReleaseWorker\UpdateBranchAliasReleaseWorker;use Symplify\MonorepoBuilder\Release\ReleaseWorker\UpdateReplaceReleaseWorker;return static function (MBConfig $mbConfig): void {// release workers - in order to execute$mbConfig->workers([UpdateReplaceReleaseWorker::class,SetCurrentMutualDependenciesReleaseWorker::class,AddTagToChangelogReleaseWorker::class,TagVersionReleaseWorker::class,PushTagReleaseWorker::class,SetNextMutualDependenciesReleaseWorker::class,UpdateBranchAliasReleaseWorker::class,PushNextDevReleaseWorker::class,]);};

我们可以根据 WordPress 插件的需要分享定制的发布程序,以增强发布流程。例如,InjectStableTagVersionInPluginReadmeFileReleaseWorker会将新版本设置为扩展程序 readme.txt 文件中的 “Stable tag” 条目:

use Nette\Utils\Strings;use PharIo\Version\Version;use Symplify\SmartFileSystem\SmartFileInfo;use Symplify\SmartFileSystem\SmartFileSystem;class InjectStableTagVersionInPluginReadmeFileReleaseWorker implements ReleaseWorkerInterface{public function __construct(// This class is provided by the Monorepo Builderprivate SmartFileSystem $smartFileSystem,) {}public function getDescription(Version $version): string{return 'Have the "Stable tag" point to the new version in the plugin\'s readme.txt file';}public function work(Version $version): void{$replacements = ['/Stable tag:\s+[a-z0-9.-]+/' => 'Stable tag: ' . $version->getVersionString(),];$this->replaceContentInFiles(['/readme.txt'], $replacements);}/*** @param string[] $files* @param array $regexPatternReplacements regex pattern to search, and its replacement*/protected function replaceContentInFiles(array $files, array $regexPatternReplacements): void{foreach ($files as $file) {$fileContent = $this->smartFileSystem->readFile($file);foreach ($regexPatternReplacements as $regexPattern => $replacement) {$fileContent = Strings::replace($fileContent, $regexPattern, $replacement);}$this->smartFileSystem->dumpFile($file, $fileContent);}}}

通过在配置列表中添加 InjectStableTagVersionInPluginReadmeFileReleaseWorker,每当执行 monorepo-builder release 命令发布新版本插件时,扩展的 readme.txt 文件中的 “Stable tag” 就会自动更新。

向 WP.org 目录发布扩展插件

我们还可以发布一个工作流程,知识兔帮助将扩展发布到 WordPress 插件目录。在远程版本库中标记项目时,以下工作流程将把 WordPress 扩展插件发布到目录中:

# See: https://github.com/10up/action-wordpress-plugin-deploy#deploy-on-pushing-a-new-tagname: Deploy to WordPress.org Plugin Directory (SVN)on:push:tags:- "*"jobs:tag:name: New tagruns-on: ubuntu-lateststeps:- uses: actions/checkout@master- name: WordPress Plugin Deployuses: 10up/action-wordpress-plugin-deploy@stableenv:SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}SVN_USERNAME: ${{ secrets.SVN_USERNAME }}SLUG: ${{ secrets.SLUG }}

此工作流程使用 10up/action-wordpress-plugin-deploy 操作,该操作会从 Git 代码库中获取代码并将其推送到 WordPress.org SVN 代码库,从而简化了操作。

下载仅供下载体验和测试学习,不得商用和正当使用。

下载体验

请输入密码查看下载!

如何免费获取密码?

点击下载

小鱼网是一个美好的开源学习社区,学习编程,学习WordPress,下载WordPress插件主题,
小鱼网 » 如何使用PHP类扩展WordPress插件

常见问题FAQ

发表回复

分享最优质的学习资料

立即查看 了解详情