Bij het ontwikkelen van een WordPress plugin is een cruciale stap het vooraf installeren van essentiële gegevens, zodat de plugin vanaf het begin soepel werkt. Neem bijvoorbeeld een plugin voor het managen van evenementen. Bij de installatie is het enorm handig als de plugin automatisch een pagina genereert met de titel Aankomende evenementen, waarop een lijst met toekomstige evenementen wordt weergegeven.
Deze voorgeconfigureerde pagina, ingesloten met een shortcode zoals [event_list number="10" scope="future" status="publish"]
, stelt gebruikers in staat om direct gebruik te maken van de mogelijkheden van de plugin zonder de documentatie door te lezen.
Het installeren van gegevens is niet alleen handig wanneer je de plugin voor het eerst installeert, maar ook wanneer je de plugin later bijwerkt. Bijvoorbeeld, als een update een feature introduceert om een kalender weer te geven, kan de plugin automatisch een nieuwe pagina maken, Evenementenkalender, die deze toevoeging laat zien door middel van een shortcode zoals [event_calendar status="publish"]
.
In het algemeen moet je bij het installeren van gegevens aan het volgende denken:
- Het genereren van nieuwe pagina’s met specifieke titels en inhoud.
- Toevoegen van items voor custom posttypes (CPT’s) die door de plugin zijn gemaakt.
- Standaardinstellingen invoegen in de tabel
wp_options
- Nieuwe mogelijkheden toewijzen aan gebruikersrollen
- Metadata toewijzen aan gebruikers, voor nieuwe of bijgewerkte functies die door de plugin worden aangeboden (gebruikers kunnen bijvoorbeeld het datumformat van een evenement wijzigen, en een standaardwaarde wordt eerst toegevoegd voor alle gebruikers)
- Categorieën aanmaken die vaak gebruikt worden binnen de context van de plugin, zoals “conferenties” of “sport”
Het installeren van gegevens moet een incrementeel proces zijn, anders zouden we dubbele vermeldingen kunnen maken.
Bijvoorbeeld, als een plugin versie 1.1 een Aankomende Evenementen pagina introduceert en een gebruiker update vanaf versie 1.0, dan moeten alleen de nieuwe gegevens die relevant zijn voor versie 1.1 worden geïnstalleerd. Deze incrementele update zorgt ervoor dat wanneer versie 1.2 uitrolt met de kalenderfunctie, alleen de nieuwe pagina Evenementenkalender wordt toegevoegd, waardoor duplicatie van de pagina Aankomende evenementen wordt voorkomen.
Daarom moet de plugin bij het updaten achterhalen welke vorige versie was geïnstalleerd en alleen de gegevens installeren die overeenkomen met de nieuwe versie(s).
Dit artikel legt uit hoe je initiële gegevens installeert en nieuwe gegevens blijft toevoegen bij verdere updates in onze WordPress plugins.
De huidige versie leveren
Om het incrementele proces aan te kunnen, moet de plugin zijn huidige versie bijhouden, die meestal wordt aangegeven in de header van het hoofdbestand van de plugin. Maar daar kunnen we natuurlijk niet direct naar verwijzen, omdat het in een PHP comment staat. Dus definiëren we deze waarde ook in een variabele en geven die door aan een Plugin
klasse die verantwoordelijk is voor initialisatie en configuratie:
<?php
/*
Plugin Name: My plugin
Version: 1.6
*/
// Same version as in the header
$pluginVersion = '1.6';
new Plugin($pluginVersion)->setup();
De klasse Plugin
, die gebruik maakt van PHP 8.0’s Constructor property promotion, slaat deze versie op, zodat we er later naar kunnen verwijzen:
<?php
class Plugin {
public function __construct(
protected string $pluginVersion,
) {}
public function setup(): void
{
// Initialization logic here...
}
// ...
}
Let op hoe de logica om de plugin te initialiseren en configureren is toegevoegd aan de methode setup
, niet aan de constructor. Dat is omdat de constructor bijwerkingen moet vermijden; anders zouden we bugs kunnen veroorzaken bij het uitbreiden of samenstellen van de klasse Plugin
.
Laten we eens kijken hoe dat zou kunnen gebeuren. Stel dat we uiteindelijk een klasse BetterPlugin
toevoegen die functionaliteit uit de klasse Plugin
samenstelt:
class BetterPlugin {
public function printSomething(): string
{
$pluginVersion = '1.0';
$plugin = new Plugin($pluginVersion);
return '<div class="wrapper">' . $plugin->printSomething() . '</div>';
}
}
Telkens wanneer new Plugin()
wordt uitgevoerd binnen printSomething
, wordt een nieuwe instantie van Plugin
aangemaakt. Als de configuratielogica zou worden toegevoegd aan de constructor, zou deze elke keer worden uitgevoerd als we een nieuw Plugin
object maken. In ons geval willen we de pagina Aankomende evenementen maar één keer maken, niet meerdere keren. Door de logica toe te voegen aan de methode setup
kunnen we dit probleem vermijden.
De vorige versie bijhouden
WordPress biedt geen handige manier om de versie op te halen van de plugin die wordt vervangen. Daarom moeten we deze waarde zelf opslaan in de wp_options
tabel van de database.
Sla de versie op onder entry "myplugin_version"
, waarbij myplugin_
de naam van de plugin is (bijvoorbeeld eventsmanager_version
). Het is belangrijk om al onze instellingen altijd vooraf te laten gaan door myplugin_
, om mogelijke conflicten te voorkomen, omdat we er niet zeker van kunnen zijn dat een andere plugin geen version
optie zal toevoegen.
Bij het laden van de plugin bij elke aanvraag, zal Plugin
al weten wat de huidige versie is (van propery $pluginVersion
eerder), en zal de laatst opgeslagen versie ophalen uit de database. Deze vergelijking bepaalt de status van de plugin:
- Nieuwe installatie: detecteert of de database een versievermelding voor de plugin mist, wat duidt op een eerste installatie (d.w.z.
$storedPluginVersion
isnull
) - Update: Wordt geïdentificeerd als de huidige versie hoger is dan de in de database opgeslagen versie, wat aangeeft dat er een upgrade nodig is.
- Anders is er geen verandering
Wanneer er een verandering is, callen we prepareAndInstallPluginSetupData
om de juiste gegevens te installeren, of het nu gaat om een nieuwe installatie (in dat geval moet het alle gegevens voor alle versies installeren) of een update (installeer alleen gegevens voor alle nieuwe versies). De nulvariabele $previousVersion
geeft aan om welke situatie het gaat ($previousVersion
is null
=> nieuwe installatie).
Na het callen van deze methode moeten we ook de huidige plugin versie opslaan in de database, wat de nieuwe “laatst opgeslagen” versie wordt. Dit moet gedaan worden na het callen van prepareAndInstallPluginSetupData
, zodat als deze methode een fout oplevert (bijvoorbeeld het gooien van een RuntimeException
) en de gegevens niet geïnstalleerd worden, de vorige versie nog steeds opgeslagen is in de database en een nieuwe ronde van het installeren van gegevens zal worden geprobeerd bij de volgende aanvraag.
<?php
class Plugin {
// ...
public function setup(): void
{
if (!is_admin()) {
return;
}
$this->managePluginDataVersioning();
}
/**
* If the plugin has just been newly-installed + activated
* or updated, install the appropriate data.
*/
protected function managePluginDataVersioning(): void
{
$myPluginVersionOptionName = 'myplugin_version';
$storedPluginVersion = get_option($myPluginVersionOptionName, null);
// Check if the main plugin has been activated or updated
$isPluginJustFirstTimeActivated = $storedPluginVersion === null;
$isPluginJustUpdated = !$isPluginJustFirstTimeActivated && $storedPluginVersion !== $this->pluginVersion;
// If there were no changes, nothing to do
if (!$isPluginJustFirstTimeActivated && !$isPluginJustUpdated) {
return;
}
\add_action(
'init',
function () use ($myPluginVersionOptionName, $storedPluginVersion): void {
$this->prepareAndInstallPluginSetupData($storedPluginVersion);
// Update on the DB
update_option($myPluginVersionOptionName, $this->pluginVersion);
}
);
}
protected function prepareAndInstallPluginSetupData(?string $previousVersion): void
{
// Installation logic...
}
}
Merk op dat prepareAndInstallPluginSetupData
(en de daaropvolgende DB update) wordt uitgevoerd op de init
action hook. Dit is om er zeker van te zijn dat alle gegevens van het CMS klaar zijn om opgevraagd en gemanipuleerd te worden.
Met name taxonomieën (tags en categorieën) kunnen niet worden benaderd vóór de init
hook. Als het installatieproces van de plugin een CPT entry moest aanmaken en er een aangepaste categorie aan moest toewijzen, dan kon dit proces pas worden uitgevoerd vanaf de init
hook.
Het opvragen van de laatst opgeslagen versie uit de DB bij elke aanvraag is niet ideaal vanuit het oogpunt van prestaties. Om dit te verbeteren, combineer je alle opties die de plugin nodig heeft in een array, sla je ze op in een enkel item en call je ze allemaal met een enkele oproep naar de DB.
Bijvoorbeeld, als de plugin ook een myplugin_date_format
optie nodig heeft om de datum van de gebeurtenis weer te geven, dan kunnen we een enkel item myplugin_options
maken met properties version
en date_format
.
Om toegang te krijgen tot de laatst opgeslagen versie, moet de PHP code dan als volgt worden aangepast:
<?php
class Plugin {
// ...
protected function managePluginDataVersioning(): void
{
$myPluginOptionsOptionName = 'myplugin_options';
$myPluginOptions = get_option($myPluginOptionsOptionName, []);
$storedPluginVersion = $myPluginOptions['version'] ?? null;
// ...
\add_action(
'init',
function () use ($myPluginOptionsOptionName, $myPluginOptions): void {
// ...
// Update on the DB
$myPluginOptions['version'] = $this->pluginVersion;
update_option($myPluginOptionsOptionName, $myPluginOptions);
}
);
}
}
Het vermijden van gelijktijdige verzoeken die dubbele gegevens installeren
De mogelijkheid bestaat dat het installatieproces meer dan eens wordt gestart als twee of meer gebruikers op precies hetzelfde moment toegang krijgen tot de wp-admin. Om te voorkomen dat dezelfde gegevens twee of meer keer worden geïnstalleerd, gebruiken we een transient als flag om alleen het eerste verzoek toe te staan om gegevens te installeren:
- Controleer of transient
myplugin_installing_plugin_setup_data
bestaat (nogmaals, deze naam moet worden voorafgegaan doormyplugin_
); zo ja, doe dan niets (omdat een ander proces de gegevens installeert) - Anders de transient in de database opslaan voor een redelijke maximumtijd om de gegevens te installeren (bijvoorbeeld 30 seconden)
- Installeer de gegevens
- Verwijder het transient event
Hier is de code:
<?php
class Plugin {
// ...
/**
* Use a transient to make sure that only one instance
* will install the data. Otherwise, two requests
* happening simultaneously might execute the logic
*/
protected function prepareAndInstallPluginSetupData(?string $previousVersion): void
{
$transientName = 'myplugin_installing_plugin_setup_data';
$transient = \get_transient($transientName);
if ($transient !== false) {
// Another instance is executing this code right now
return;
}
\set_transient($transientName, true, 30);
$this->installPluginSetupData($previousVersion);
\delete_transient($transientName);
}
protected function installPluginSetupData(?string $previousVersion): void
{
// Do something...
}
}
Gegevens installeren voor alle versies
Zoals eerder vermeld, moeten we bij het bijwerken van de plugin alleen de gegevens voor de nieuwe versies installeren, niet voor alle versies. Dat betekent dat we per versie moeten beheren welke gegevens we installeren.
In de onderstaande code geeft de array $versionCallbacks
aan welke functie moet worden uitgevoerd voor elke versie, waarbij de functie de logica uitvoert om de gegevens te installeren. We itereren de lijst van alle versies, vergelijken elke versie met de vorige versie met version_compare
en als deze groter is, voeren we de bijbehorende functie uit om de bijbehorende gegevens te installeren.
Merk op dat als $previousVersion
null
is (het is dus een nieuwe installatie), alle functies worden uitgevoerd.
class Plugin {
/**
* Provide the installation in stages, version by version, to
* be able to execute it both when installing/activating the plugin,
* or updating it to a new version with setup data.
*
* The plugin's setup data will be installed if:
*
* - $previousVersion = null => Activating the plugin for first time
* - $previousVersion < someVersion => Updating to a new version that has data to install
*/
protected function installPluginSetupData(?string $previousVersion): void
{
$versionCallbacks = [
'1.1' => $this->installPluginSetupDataForVersion1Dot1(...),
'1.2' => $this->installPluginSetupDataForVersion1Dot2(...),
// ... Add more versions
];
foreach ($versionCallbacks as $version => $callback) {
/**
* If the previous version is provided, check if the corresponding update
* has already been performed, then skip
*/
if ($previousVersion !== null && version_compare($previousVersion, $version, '>=')) {
continue;
}
$callback();
}
}
protected function installPluginSetupDataForVersion1Dot1(): void
{
// Do something...
}
protected function installPluginSetupDataForVersion1Dot2(): void
{
// Do something...
}
}
Gegevens installeren voor elke specifieke versie
Tot slot moeten we de eigenlijke gegevens installeren (een pagina maken, een CPT vermelding, een optie toevoegen, enzovoort) voor elke versie.
In deze code voegen we de pagina Upcoming Events toe voor de plugin voor evenementenbeheer, voor v1.1
:
class Plugin {
// ...
protected function installPluginSetupDataForVersion1Dot1(): void
{
\wp_insert_post([
'post_status' => 'publish',
'post_type' => 'page',
'post_title' => \__('Upcoming Events', 'myplugin'),
'post_content' => '[event_list number="10" scope="future"]',
]);
}
// ...
}
Vervolgens maken we de pagina Event Calendar voor v1.2
(in dit geval gebruiken we Gutenberg blokken op de pagina en voegen we een custom blok toe met de naam event-calendar
):
class Plugin {
// ...
protected function installPluginSetupDataForVersion1Dot2(): void
{
\wp_insert_post([
'post_status' => 'publish',
'post_type' => 'page',
'post_title' => \__('Events Calendar', 'myplugin'),
'post_content' => serialize_blocks([
[
'blockName' => 'myplugin/event-calendar',
'attrs' => [
'status' => 'publish',
],
'innerContent' => [],
],
]),
]);
}
}
Alle code bij elkaar
We zijn klaar! De hele PHP code voor de klasse Plugin
, die de logica bevat om de pluginversie bij te houden en de juiste gegevens te installeren, is de volgende:
<?php
class Plugin {
public function __construct(
protected string $pluginVersion,
) {
}
public function setup(): void
{
if (!is_admin()) {
return;
}
$this->managePluginDataVersioning();
}
/**
* If the plugin has just been newly-installed + activated
* or updated, install the appropriate data.
*/
protected function managePluginDataVersioning(): void
{
$myPluginVersionOptionName = 'myplugin_version';
$storedPluginVersion = get_option($myPluginVersionOptionName, null);
// Check if the main plugin has been activated or updated
$isPluginJustFirstTimeActivated = $storedPluginVersion === null;
$isPluginJustUpdated = !$isPluginJustFirstTimeActivated && $storedPluginVersion !== $this->pluginVersion;
// If there were no changes, nothing to do
if (!$isPluginJustFirstTimeActivated && !$isPluginJustUpdated) {
return;
}
\add_action(
'init',
function () use ($myPluginVersionOptionName, $storedPluginVersion): void {
$this->prepareAndInstallPluginSetupData($storedPluginVersion);
// Update on the DB
update_option($myPluginVersionOptionName, $this->pluginVersion);
}
);
}
/**
* Use a transient to make sure that only one instance
* will install the data. Otherwise, two requests
* happening simultaneously might both execute
* this logic
*/
protected function prepareAndInstallPluginSetupData(?string $previousVersion): void
{
$transientName = 'myplugin_installing_plugin_setup_data';
$transient = \get_transient($transientName);
if ($transient !== false) {
// Another instance is executing this code right now
return;
}
\set_transient($transientName, true, 30);
$this->installPluginSetupData($previousVersion);
\delete_transient($transientName);
}
/**
* Provide the installation in stages, version by version, to
* be able to execute it both when installing/activating the plugin,
* or updating it to a new version with setup data.
*
* The plugin's setup data will be installed if:
*
* - $previousVersion = null => Activating the plugin for first time
* - $previousVersion < someVersion => Updating to a new version that has data to install
*/
protected function installPluginSetupData(?string $previousVersion): void
{
$versionCallbacks = [
'1.1' => $this->installPluginSetupDataForVersion1Dot1(...),
'1.2' => $this->installPluginSetupDataForVersion1Dot2(...),
// ... Add more versions
];
foreach ($versionCallbacks as $version => $callback) {
/**
* If the previous version is provided, check if the corresponding update
* has already been performed, then skip
*/
if ($previousVersion !== null && version_compare($previousVersion, $version, '>=')) {
continue;
}
$callback();
}
}
protected function installPluginSetupDataForVersion1Dot1(): void
{
\wp_insert_post([
'post_status' => 'publish',
'post_type' => 'page',
'post_title' => \__('Upcoming Events', 'myplugin'),
'post_content' => '[event_list number="10" scope="future" status="publish"]',
]);
}
protected function installPluginSetupDataForVersion1Dot2(): void
{
\wp_insert_post([
'post_status' => 'publish',
'post_type' => 'page',
'post_title' => \__('Events Calendar', 'myplugin'),
'post_content' => serialize_blocks([
[
'blockName' => 'myplugin/event-calendar',
'attrs' => [
'status' => 'publish',
],
'innerContent' => [],
],
]),
]);
}
}
Samenvatting
WordPress plugins moeten vaak gegevens installeren bij de installatie. Als nieuwere versies van de plugin nieuwe features bieden, kan het bovendien nodig zijn dat de plugin gegevens installeert bij updates.
In dit artikel hebben we geleerd hoe we versies kunnen bijhouden en de juiste gegevens voor onze plugins kunnen installeren.
Heb jij een WordPress plugin die baat kan hebben bij het installeren van gegevens? Laat het ons weten in de comments.
Laat een reactie achter