如何使用PHP类扩展WordPress插件

2024年 6月 27日 97.5k 0

如何使用PHP类扩展WordPress插件-1

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

  • 为扩展插件设置钩子(动作和过滤器),以便其注入自己的功能
  • 提供扩展插件可以继承的 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 extensions
    do_action('gatographql:initializeExtension');
    // 3. Configure main plugin components
    $this->configureComponents();
    // 4. Configure extension components
    do_action('gatographql:configureExtensionComponents');
    // 5. Configure main plugin
    $this->configure();
    // 6. Configure extension
    do_action('gatographql:configureExtension');
    // 7. Boot main plugin
    $this->boot();
    // 8. Boot extension
    do_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 extensions
    add_action(
    'gatographql:initializeExtension',
    $this->initialize(...)
    );
    // 4. Configure extension components
    add_action(
    'gatographql:configureExtensionComponents',
    $this->configureComponents(...)
    );
    // 6. Configure extension
    add_action(
    'gatographql:configureExtension',
    $this->configure(...)
    );
    // 8. Boot extension
    add_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<class-string<ModuleInterface>,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<class-string<ModuleInterface>> 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 methods
    add_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(
    '<div class="notice notice-error"><p>%s</p></div>',
    sprintf(
    __('Plugin <strong>%s</strong> is not installed or activated. Without it, plugin <strong>%s</strong> 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 autoloader
    require_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 <strong>%s</strong> requires plugin <strong>%s</strong> to satisfy version constraint <code>%s</code>, but the current version <code>%s</code> 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(
    '<div class="notice notice-error"><p>%s</p></div>',
    $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:
    - completed
    jobs:
    provide_data:
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    name: Retrieve the GitHub Action artifact URLs to install in InstaWP
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: shivammathur/setup-php@v2
    with:
    php-version: 8.1
    coverage: none
    env:
    COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    - uses: "ramsey/composer-install@v2"
    - name: Retrieve artifact URLs from GitHub workflow
    uses: actions/github-script@v6
    id: artifact-url
    with:
    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 InstaWP
    run: echo "Artifact URL for InstaWP - ${{ steps.artifact-url.outputs.result }}"
    shell: bash
    outputs:
    artifact_url: ${{ steps.artifact-url.outputs.result }}
    process:
    needs: provide_data
    name: Launch InstaWP site from template 'integration-tests' and execute integration tests against it
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: shivammathur/setup-php@v2
    with:
    php-version: 8.1
    coverage: none
    env:
    COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    - uses: "ramsey/composer-install@v2"
    - name: Create InstaWP instance
    uses: instawp/wordpress-testing-automation@main
    id: create-instawp
    with:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    INSTAWP_TOKEN: ${{ secrets.INSTAWP_TOKEN }}
    INSTAWP_TEMPLATE_SLUG: "integration-tests"
    REPO_ID: 25
    INSTAWP_ACTION: create-site-template
    ARTIFACT_URL: ${{ needs.provide_data.outputs.artifact_url }}
    - name: InstaWP instance URL
    run: echo "InstaWP instance URL - ${{ steps.create-instawp.outputs.instawp_url }}"
    shell: bash
    - name: Extract InstaWP domain
    id: 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 tests
    run: |
    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 instance
    uses: instawp/wordpress-testing-automation@main
    id: destroy-instawp
    if: ${{ always() }}
    with:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    INSTAWP_TOKEN: ${{ secrets.INSTAWP_TOKEN }}
    INSTAWP_TEMPLATE_SLUG: "integration-tests"
    REPO_ID: 25
    INSTAWP_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 Builder
    private 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<string,string> $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-tag
    name: Deploy to WordPress.org Plugin Directory (SVN)
    on:
    push:
    tags:
    - "*"
    jobs:
    tag:
    name: New tag
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - name: WordPress Plugin Deploy
    uses: 10up/action-wordpress-plugin-deploy@stable
    env:
    SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
    SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
    SLUG: ${{ secrets.SLUG }}
    

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

    小结

    在为 WordPress 创建可扩展插件时,我们的目标是尽可能方便第三方开发人员对其进行扩展,从而最大限度地围绕我们的插件培育出一个充满活力的生态系统。

    虽然提供大量文档可以指导开发人员如何扩展插件,但更有效的方法是为开发、测试和发布扩展提供必要的 PHP 代码和工具。

    通过在插件中直接包含扩展所需的附加代码,我们简化了开发人员的开发过程。

    您打算让自己的 WordPress 插件具有可扩展性吗?请在评论区告诉我们。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论