The design and development of a WordPress plugin which can be expanded with the aid of PHP classes (r)

Aug 15, 2024

-sidebar-toc> -language-notice>

  1. When installing hooks (actions and filters) for plugin extensions that add functions of their own
  2. When offering PHP classes, plugin extensions could be used to

The first relies on documentation, which describes the hooks available and how they're employed. The other, however it comes with software that is extensible to enhance functionality. This means that there is no need for a detailed description. This is advantageous since writing documentation as well as code could have a negative impact on the administration of plugins, as well as the distribution.

We'll look at methods for making this happen to build a community around WordPress. WordPress plugin.

The basic PHP classes are used by WordPress. WordPress plugin

Let's see what this could be possible to incorporate into the open-source Gato GraphQL plugin.

AbstractPlugin class:

AbstractPlugin can be known as an extension plugin that works with 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 expands AbstractPlugin up to a point that it allows it to replicate the primary functions:

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

It is important to remember that this is due to the nature of the plugin. AbstractExtension is a key component of the plugin. It permits you to start by joining an extension. However, it is only used to add extensions and it's not a plugin by itself.

AbstractPlugin is in AbstractPlugin is one of AbstractPlugin classes. AbstractPlugin AbstractPlugin is an abstract class that is a shared initalization procedure that runs at different times. The process is created on the basis of an ancestral, but they are utilized by other classes which inherit on the basis of the duration of time they have been in existence.

The plugin's primary and extensions are launched by running through the initialization process in the class used within the main WordPress plugin's program.

As an example, in Gato GraphQL, this is executed via gatographql.php:

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

Procedure for setting up

When an extension is the parent, the configuration includes the same logic for both the extension and the plugin in the case of unregistering them once the plugin was deleted. The method does not have to be to be an absolute one but can be modified through inheriting classes to increase their capability:

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 do you set up the primary plugin

The primary configuration method begins the complete procedure of setting up the plugin. The primary way to execute tasks is by executing procedures such as the initialization, configureComponents, configure, and begin and end actions hooks that 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'); // ... // ...

The process of setting up extensions

AbstractExtension class AbstractExtension class is able to apply its logic by using hooks that are in the 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 techniques to beginning, configureComponents, configure, and begin are all used by the primary plugin, as extensions. They can all have the similar principle. The logic utilized in these techniques is part 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 is also able to have its own implementation

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

Methods which initiate, configureComponents and the time they begin will be determined by parents. 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

Each method can be altered using AbstractMainPlugin or AbstractExtension to add features of your choice.

The plugin's primary configuration method also removes all cached WordPress instances when the plugin or extension, is disabled or enabled:

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

In the same manner, deactivate procedure gets rid of cache and boots in addition to performing additional hooks that are unique to the plugin. They are however only meant 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; );

Checking for the dependency, and then indicating that it is a version

Since the extension is derived in part from PHP class used in the extension, it's crucial to make sure that the correct version of the extension has been installed. In the event of a oversight, it may cause problems which could cause the loss of the website.

This is the case for example, in the event that AbstractExtension is updated to version 4.0.0, the AbstractExtension class, it is changed to make significant modifications that break the code and can be downloaded with upgrade 4.0.0 from the earlier version 3.4.0. 4.0.0 from the earlier version 3.4.0, loading the extension with no verification of the version could result in an PHP error, which stops WordPress to load.

In order to prevent the possibility of this happening to prevent this from happening, the extension is running version 3.x.x. If the version 4.0.0 is installed the extension is removed in order to stop any mistakes.

It is possible to check this by following the method that is described in this article. It it is carried out by the plugins_loaded hook. plugins_loaded hook (since the core extension plugin has been in place up to the present) is located in the extension's plugin's primary file. This logic allows users to access extensions by utilizing extensions manager classes. extensions administration class is a part of the plugin's core. It is responsible for managing extensions.

Code >/** Create and set the extension. This is the process is to add( "plugins_loaded" function () Extension is removed. /*** The name of the extension as well as 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 allow the extension to be functional. */ $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 isn't working and it doesn't seem to be activated and cannot be installed, the plugin is not activated. '), __('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 constraint ^1.0 of the main extension (using Composer's version constraints). If Version 2.0.0 of Gato GraphQL is installed, but not turned off, then the extension has 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 effective, it will submit an error to WP's administrator WP and result in an incorrect. String

Integration testing by using WordPress server WordPress server

For automated testing during the CI/CD process, you need to connect via an internet connection that connects to the CI/CD server. Software like InstaWP can be used to build Sandbox websites with WordPress and could be utilized 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 

The workflow downloads files from the .zip file via the nightly Link which allows access to artifacts via GitHub and does not require users to log in. This workflow can also be helpful in establishing the installation of InstaWP.

Extension plugins are easily accessible.

There are tools to assist in the release of extensions and make this process as easy as is possible.

It's an extension of the Monorepo Builder is a software that is used to manage each PHP project. Additionally, it is a component of WordPress. WordPress plugin. It is compatible with the monorepo-builder release command that lets you publish an update on your project. You can alter or increase the size of the major, minor or patch parts of an update based upon the nature of the language used for version.

The command can be used to run a set of release workers comprising PHP classes that perform certain actions. There are built-in builders that are default. One of them creates one that creates the Git tags that is changed to the latest version. Others builders push tags to remote repositories. The custom-designed worker can be added before, following or even in between actions.

The release worker is configured by the use of 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, ]); ; 

Our in-house release team that assists in the process of releasing specifically designed to meet needs of WordPress. 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 installed through the WP.org directory.

You can also opt to follow the path of creating an automated workflow that will assist in releasing this extension in WordPress's WordPress the directory of plugins. After tagging the plugin on the remote repository, and adhering to the steps following will allow you to upload your WordPress extension into 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 extensions-friendly plugins for WordPress we'll allow the third party developers to develop extensions for the plugin to increase the likelihood of creating communities for the plugins that we create.

The information provided by the documentation can assist developers to understand the most effective ways to improve the functionality of this plugin. method is to provide the complete set of PHP tools as well as the code for creating, testing the extensions, and eventually it is time to release them.

Integrating any additional code that is required by extensions directly into our extension plugin is more convenient for the extension developers.

     Have you considered creating your own WordPress plugin extensible? Tell us your ideas by using the comment section.

Leonardo Losoviz

Leo is a blogger. He is a journalist who an author who writes on the latest Web methods for development, 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 this website.

This post was first seen here. here

Article was first seen on here