The development of the development of a WordPress plugin that is extensible with the aid of PHP classes (r)

Aug 15, 2024

-sidebar-toc> -language-notice>

  1. In installing hooks (actions and filters) to plugin extensions, which add their own functions
  2. In offering PHP classes that plugin extensions may be able to

The first method relies more on documentation that outlines the available hooks and the way they're used. The second, in contrast, comes with ready-to-use software that is extensible to enhance functionality. This eliminates the requirement for a detailed documentation. This is advantageous since the creation of documentation as well as code could have a negative impact on the administration of plugins as much as the distribution.

Let's look at ways for doing this to create an community around WordPress. WordPress plugin.

The basic PHP classes are required by WordPress. WordPress plugin

Let's see what this could be possible to implement in the open source Gato GraphQL plugin.

AbstractPlugin class:

AbstractPlugin is referred to as an extension plug-in that is compatible to Gato GraphQL plugin and its extensions:

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 class:

AbstractMainPlugin extends AbstractPlugin to the point that it allows it to mirror the main features:

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 class:

This is also true of AbstractExtension. AbstractExtension is able to expand AbstractPlugin to become an extension plugin:

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

Important to keep in mind that this is due to the fact that AbstractExtension is an integral part of this plugin. It allows you to begin and join an extension. It's however only utilized for extensions, and not the plugin in itself.

AbstractPlugin is one of the AbstractPlugin classes. AbstractPlugin AbstractPlugin is a class, which is a shared initalization procedure which is executed at various times. The procedure is designed at the level of ancestral, but they are also used by classes that inherit based on the length of their existence.

The plugin's primary and extensions are launched by running the initialization procedure in the class invoked within the core WordPress plugin's program.

As an example, in Gato GraphQL, this is carried out through gatographql.php:

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

Method for setting up

If an extension is the parent, the configuration incorporates the same logic between both the extension and the plugin, for example unregistering them after the plugin has been deleted. This method is not required to be an absolute one, and can be modified by inheriting classes in order to improve their capabilities:

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); 

How to set up the primary plugin

The main configuration method starts the entire process of setting up the plugin. The primary way to execute functions is through procedures like the initialization, configureComponents, configure, and begin as well as activating actions hooks to extend the functionality of extensions.

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'); // ... // ...

Extension setup process

AbstractExtension class AbstractExtension class is able to implement its logic using the hooks in the respective classes:

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);

The methods for begin, configureComponents, configure, and start are all shared by the primary plugin, like extensions. These plugins may be built on the same logic. The logic used by these methods is an element of the AbstractPlugin class.

For example, the configure method configures the plugin or extensions, calling callPluginInitializationConfiguration, which has different implementations for the main plugin and extensions and is defined as abstract and getModuleClassConfiguration, which provides a default behavior but can be overridden if needed:

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 []; 

The main plugin provides its implementation for callPluginInitializationConfiguration:

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

The extension class also has its own implementation

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

Methods which initiate, configureComponents and when they start will be decided by parent. They may be modified by inheriting classes

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

Every method is able to be modified making use of AbstractMainPlugin or AbstractExtension to include functions of your choosing.

The plugin's main configuration method removes also all cached WordPress instances where the plugin or extension, is disabled or activated:

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 // ... 

Similar to the way in the same way, deactivate procedure eliminates cache and boots as well as performing additional hooks, which are specific to the plugin. However, they are exclusively intended for:

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; );

Verifying the dependency and stating that it is a version

Since the extension is derived in part from PHP class utilized by the extension, it's important to ensure that the right version of the plugin has been installed. In the event of a mistake, it could create problems which could cause the loss of the website.

This is the case for instance, if AbstractExtension has been updated with the AbstractExtension class is modified to incorporate significant changes that break the code and is made available with the update 4.0.0 from the earlier version 3.4.0. 4.0.0 from the older version 3.4.0, loading the extension with no verification of the version may result in an PHP error that stops WordPress to load.

In order to prevent this from occurring, it's essential that the extension ensures that it runs version 3.x.x. If version 4.0.0 is installed the extension will be removed to prevent mistakes.

The extension is able to test this via the procedure described in this article and is executed by plugins_loaded hook. plugins_loaded hook (since the extension's core plugin has been in place up until this point) in the extension's plugin's main file. The logic is able to get access to extensions through extensions manager classes. extensions management class is part of the plugin's core. It manages extensions

Code >/** Create and set up an extension. the action of adding( "plugins_loaded" Funktion () Extension is deleted. /*** The name of the extension and its version. It is suggested to choose an extension suffix that is stable since it's recognized by Composer. */ $extensionVersion = '1.1.0';$extensionName is __('Gato GraphQL - Extension Template'); *** The minimum version required from Gato GraphQL is 1.1.0. Gato GraphQL plugin * to let the extension be operational. */ $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. If the plugin doesn't work and the plugin doesn't appear to be activated, it cannot be installed. '), __('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(); );

It's important to remember that the extension is able to declare its dependence on the version of the constraint ^1.0 of the main extension (using Composer's version constraints). If the version 2.0.0 of Gato GraphQL is installed, but not disabled, then it's been enabled.

The version constraint is validated via the ExtensionManager::assertIsValid method, which calls Semver::satisfies (provided by the 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 is not successful, it will report an error to WP's administrator WP which will result in an incorrect. String

Testing integrations using WordPress server WordPress server

To automate the testing process during the CI/CD process, you must connect using an internet connection which connects to the CI/CD server. Software such as InstaWP lets you build Sandbox sites with WordPress and can be used to serve as Sandboxes.

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 

This workflow downloads files in the .zip file via nightly Link which allows access to artifacts through GitHub and without the requirement to sign in. The workflow is also helpful in establishing the installation of InstaWP.

Extension plugins are readily available.

There are tools available to assist in the release of extensions while also making the process as automated as possible.

It is an extension to the Monorepo Builder is a tool that is used to manage every PHP project. It also is also a part of WordPress. WordPress plugin. It comes with the monorepo-builder release command, which lets you publish an update on your project. It can increase or decrease the size of the minor, major or patch components of the update based upon the the semantics of the language that is used for version.

The command has the ability to run a set of release workers which include PHP classes which perform specific procedures. There are built-in builders by default, one which creates one that creates a Git tag that is updated to the most recent version. Other builders push tags to remote repository. Custom-designed workers can be added prior to, following or even in between the actions.

The release worker can be configured by a configuration file

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, ]); ; 

We have an in-house release team to aid in the process of releasing specifically designed to meet the demands of the WordPress plugin. For example, the InjectStableTagVersionInPluginReadmeFileReleaseWorker sets the new version as the "Stable tag" entry in the extension's readme.txt file:

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 $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);

By adding InjectStableTagVersionInPluginReadmeFileReleaseWorker to the configuration list, whenever executing the monorepo-builder release command to release a new version of the plugin, the "Stable tag" in the extension's readme.txt file will be automatically updated.

The extension plugin is downloaded via the WP.org directory

There is also the option to follow the path of creating an automated workflow to aid in the release of the extension into WordPress's WordPress the plugin directory. After tagging the plugin on the remote repository and then adhering to the procedure below will allow you to upload your WordPress extension to the directory:

# 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 

Summary

If we develop an extension-friendly plugin to WordPress we'll allow the third party developers to develop extensions for the plugin to increase the likelihood of creating communities for the plugins we develop.

The documentation provided can help developers understand the best ways to improve the functionality of this plugin. method is to provide all PHP tools and the necessary code for creating, testing the extensions, and finally, releasing them.

Incorporating additional code required by extensions directly in our plugin it is much easier for extension creators.

     Have you considered creating your WordPress plugin more extensible? Let us know your thoughts via the comment box.

Leonardo Losoviz

Leo is blogger, who is a writer who is a writer who writes about the latest Web techniques for developing, often with PHP, WordPress and GraphQL. Leo is accessible via leoloso.com and twitter.com/losoviz.

The article was posted on this site.

The article was published on here

This post was first seen on here