
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,
$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);
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);
}
// ...
}
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(
protected MainPluginInitializationConfigurationInterface $pluginInitializationConfiguration,
abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
{
public function __construct(
string $pluginFile,
string $pluginVersion,
?string $pluginName,
protected MainPluginInitializationConfigurationInterface $pluginInitializationConfiguration,
) {
parent::__construct(
$pluginFile,
$pluginVersion,
$pluginName,
);
}
// ...
}
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(
protected ?ExtensionInitializationConfigurationInterface $extensionInitializationConfiguration,
abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface
{
public function __construct(
string $pluginFile,
string $pluginVersion,
?string $pluginName,
protected ?ExtensionInitializationConfigurationInterface $extensionInitializationConfiguration,
) {
parent::__construct(
$pluginFile,
$pluginVersion,
$pluginName,
);
}
// ...
}
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
中完成的:
$pluginVersion = '2.4.0';
$pluginName = __('Gato GraphQL', 'gatographql');
PluginApp::getMainPluginManager()->register(new Plugin(
$pluginFile = __FILE__;
$pluginVersion = '2.4.0';
$pluginName = __('Gato GraphQL', 'gatographql');
PluginApp::getMainPluginManager()->register(new Plugin(
$pluginFile,
$pluginVersion,
$pluginName
))->setup();
$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(
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);
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);
}
}
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
方法初始化應用程式的生命週期。它通過initialize
、configureComponents
、configure
和boot
等方法執行主外掛的功能,併為擴充套件觸發相應的操作鉤子:
abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
public function setup(): void
add_action('plugins_loaded', function (): void
// 1. Initialize main plugin
// 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
// 6. Configure extension
do_action('gatographql:configureExtension');
do_action('gatographql:bootExtension');
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');
}
// ...
}
// ...
}
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
add_action('plugins_loaded', function (): void
// 2. Initialize extensions
'gatographql:initializeExtension',
// 4. Configure extension components
'gatographql:configureExtensionComponents',
$this->configureComponents(...)
// 6. Configure extension
'gatographql:configureExtension',
'gatographql:bootExtension',
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);
}
}
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);
}
}
initialize
, configureComponents
, configure
, 和 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
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 [];
}
}
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 AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface
{
// ...
protected function callPluginInitializationConfiguration(): void
{
$this->pluginInitializationConfiguration->initialize();
}
}
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();
abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface
{
// ...
protected function callPluginInitializationConfiguration(): void
{
$this->extensionInitializationConfiguration?->initialize();
}
}
abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface
{
// ...
protected function callPluginInitializationConfiguration(): void
{
$this->extensionInitializationConfiguration?->initialize();
}
}
方法 initialize
、configureComponents
和 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
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
}
}
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
// Main-plugin specific methods
function (string $pluginFile): void {
$this->maybeRegenerateContainerWhenPluginActivatedOrDeactivated($pluginFile);
function (string $pluginFile): void {
$this->maybeRegenerateContainerWhenPluginActivatedOrDeactivated($pluginFile);
public function maybeRegenerateContainerWhenPluginActivatedOrDeactivated(string $pluginFile): void
// Removed code for simplicity
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
}
// ...
}
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
$this->removeTimestamps();
protected function removeTimestamps(): void
$userSettingsManager = UserSettingsManagerFacade::getInstance();
$userSettingsManager->removeTimestamps();
public function boot(): void
function (string $classes): string {
$extensions = PluginApp::getExtensionManager()->getExtensions();
$commercialExtensionActivatedLicenseObjectProperties = SettingsHelpers::getCommercialExtensionActivatedLicenseObjectProperties();
foreach ($extensions as $extension) {
$extensionCommercialExtensionActivatedLicenseObjectProperties = $commercialExtensionActivatedLicenseObjectProperties[$extension->getPluginSlug()] ?? null;
if ($extensionCommercialExtensionActivatedLicenseObjectProperties === null) {
return $classes . ' is-gatographql-customer';
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;
}
);
}
}
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
* 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) {
'<div class="notice notice-error"><p>%s</p></div>',
__('Plugin <strong>%s</strong> is not installed or activated. Without it, plugin <strong>%s</strong> will not be loaded.'),
$extensionManager = \GatoGraphQL\GatoGraphQL\PluginApp::getExtensionManager();
if (!$extensionManager->assertIsValid(
GatoGraphQLExtension::class,
$gatoGraphQLPluginVersionConstraint
// Load Composer’s autoloader
require_once(__DIR__ . '/vendor/autoload.php');
// Create and set-up the extension instance
$extensionManager->register(new GatoGraphQLExtension(
/**
* 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();
}
);
/**
* 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 $extensionVersion,
?string $extensionName = null,
?string $mainPluginVersionConstraint = null,
$mainPlugin = \GatoGraphQL\GatoGraphQL\PluginApp::getMainPluginManager()->getPlugin();
$mainPluginVersion = $mainPlugin->getPluginVersion();
$mainPluginVersionConstraint !== null && !Semver::satisfies(
$mainPluginVersionConstraint
$this->printAdminNoticeErrorMessage(
__('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(),
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>',
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;
});
}
}
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)
workflows: [Generate plugins]
if: ${{ github.event.workflow_run.conclusion == 'success' }}
name: Retrieve the GitHub Action artifact URLs to install in InstaWP
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: "ramsey/composer-install@v2"
- name: Retrieve artifact URLs from GitHub workflow
uses: actions/github-script@v6
const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
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'
"https://downloads.wordpress.org/plugin/gatographql.latest-stable.zip"
return artifactURLs.join(',');
- name: Artifact URL for InstaWP
run: echo "Artifact URL for InstaWP - ${{ steps.artifact-url.outputs.result }}"
artifact_url: ${{ steps.artifact-url.outputs.result }}
name: Launch InstaWP site from template 'integration-tests' and execute integration tests against it
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: "ramsey/composer-install@v2"
- name: Create InstaWP instance
uses: instawp/wordpress-testing-automation@main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INSTAWP_TOKEN: ${{ secrets.INSTAWP_TOKEN }}
INSTAWP_TEMPLATE_SLUG: "integration-tests"
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 }}"
- name: Extract InstaWP domain
id: extract-instawp-domain
instawp_domain="$(echo "${{ steps.create-instawp.outputs.instawp_url }}" | sed -e s#https://##)"
echo "instawp-domain=$(echo $instawp_domain)" >> $GITHUB_OUTPUT
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
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INSTAWP_TOKEN: ${{ secrets.INSTAWP_TOKEN }}
INSTAWP_TEMPLATE_SLUG: "integration-tests"
INSTAWP_ACTION: destroy-site
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
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
UpdateReplaceReleaseWorker::class,
SetCurrentMutualDependenciesReleaseWorker::class,
AddTagToChangelogReleaseWorker::class,
TagVersionReleaseWorker::class,
PushTagReleaseWorker::class,
SetNextMutualDependenciesReleaseWorker::class,
UpdateBranchAliasReleaseWorker::class,
PushNextDevReleaseWorker::class,
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,
]);
};
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 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
'/Stable tag:\s+[a-z0-9.-]+/' => 'Stable tag: ' . $version->getVersionString(),
$this->replaceContentInFiles(['/readme.txt'], $replacements);
* @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);
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);
}
}
}
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)
- uses: actions/checkout@master
- name: WordPress Plugin Deploy
uses: 10up/action-wordpress-plugin-deploy@stable
SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
SLUG: ${{ secrets.SLUG }}
# 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 }}
# 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 外掛具有可擴充套件性嗎?請在評論區告訴我們。
評論留言