{"id":58652,"date":"2024-03-04T14:14:30","date_gmt":"2024-03-04T13:14:30","guid":{"rendered":"https:\/\/kinsta.com\/nl\/?p=58652&#038;preview=true&#038;preview_id=58652"},"modified":"2024-03-12T18:30:28","modified_gmt":"2024-03-12T17:30:28","slug":"pro-gratis-versies-wordpress-plugin","status":"publish","type":"post","link":"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/","title":{"rendered":"Zo bouw je een WordPress plugin die extensies ondersteunt"},"content":{"rendered":"<p>In het WordPress ecosysteem is het gebruik van een <a href=\"https:\/\/kinsta.com\/nl\/blog\/geld-verdienen-met-apps\/\">freemium model<\/a> een veelgebruikte methode voor het promoten en te gelde maken van commerci\u00eble plugins. Deze aanpak houdt in dat je een basisversie van de plugin gratis uitbrengt &#8211; meestal via de <a href=\"https:\/\/wordpress.org\/plugins\/\" target=\"_blank\" rel=\"noopener noreferrer\">WordPress plugin directory<\/a> &#8211; en uitgebreide features aanbiedt via een PRO versie of add-ons, die meestal verkocht worden op de website van de plugin.<\/p>\n<p>Er zijn drie verschillende manieren om commerci\u00eble functies te integreren in een freemium model:<\/p>\n<ol>\n<li>Herberg deze commerci\u00eble functies in de gratis plugin en activeer ze alleen als de commerci\u00eble versie op de website is ge\u00efnstalleerd of als er een commerci\u00eble licentiesleutel is verstrekt.<\/li>\n<li>Maak de gratis en PRO versies als onafhankelijke plugins, waarbij de PRO versie ontworpen is om de gratis versie te vervangen, zodat er altijd maar \u00e9\u00e9n versie ge\u00efnstalleerd is.<\/li>\n<li>Installeer de PRO versie naast de gratis plugin en breid de functionaliteit uit. Hiervoor moeten beide versies aanwezig zijn.<\/li>\n<\/ol>\n<p>De eerste benadering is echter <a href=\"https:\/\/developer.wordpress.org\/plugins\/wordpress-org\/detailed-plugin-guidelines\/#5-trialware-is-not-permitted\" target=\"_blank\" rel=\"noopener noreferrer\">onverenigbaar met de richtlijnen<\/a> voor plugins die worden gedistribueerd via de WordPress plugin directory, omdat deze regels verbieden om functies op te nemen die beperkt of vergrendeld zijn totdat er een betaling of upgrade is gedaan.<\/p>\n<p>Dan blijven de laatste twee opties over, die voor- en nadelen hebben. De paragrafen hieronder leggen uit waarom de laatste strategie, &#8220;PRO naast gratis&#8221;, onze beste keuze is.<\/p>\n<p>Maar laten we eerst kijken naar de tweede optie, &#8220;PRO die de gratis versie vervangt&#8221;, de gebreken ervan en waarom het uiteindelijk niet wordt aanbevolen.<\/p>\n<p>Daarna gaan we dieper in op &#8220;PRO naast gratis&#8221;, waarbij we uitleggen waarom dit de beste keuze is.<\/p>\n<div><\/div><kinsta-auto-toc heading=\"Table of Contents\" exclude=\"last\" list-style=\"arrow\" selector=\"h2\" count-number=\"-1\"><\/kinsta-auto-toc>\n<h2>Voordelen van de &#8220;PRO die de gratis versie vervangt&#8221; strategie<\/h2>\n<p>De &#8220;PRO die de gratis versie vervangt&#8221; strategie is relatief eenvoudig te implementeren omdat de ontwikkelaars een enkele codebase kunnen gebruiken voor beide plugins (gratis en PRO) en er twee uitgangen van kunnen maken, waarbij de gratis (of &#8220;standaard&#8221;) versie eenvoudigweg een subset van de code bevat en de PRO versie alle code.<\/p>\n<p>De codebase van het project zou bijvoorbeeld kunnen worden opgesplitst in <code>standard\/<\/code> en <code>pro\/<\/code> directories. De plugin zou altijd de standaardcode laden, waarbij de PRO code voorwaardelijk wordt geladen, gebaseerd op de aanwezigheid van de betreffende map:<\/p>\n<pre><code class=\"language-php\">\/\/ Main plugin file: myplugin.php\n\n\/\/ Always load the standard plugin's code\nrequire_once __DIR__ . '\/standard\/load.php';\n\n\/\/ Load the PRO plugin's code only if the folder exists\n$proFolder = __DIR__ . '\/pro';\nif (file_exists($proFolder)) {\n  require_once $proFolder . '\/load.php';\n}\n<\/code><\/pre>\n<p>Wanneer we dan de plugin genereren via een <a href=\"https:\/\/kinsta.com\/nl\/blog\/ci-cd-pipeline-maken\/\">Continuous Integration<\/a> tool, kunnen we de twee assets <code>myplugin-standard.zip<\/code> en <code>myplugin-pro.zip<\/code> maken van dezelfde sourcecode.<\/p>\n<p>Als we het project hosten op GitHub en de assets genereren via GitHub Actions, dan doet de volgende workflow het werk:<\/p>\n<pre><code class=\"language-yaml\">name: Generate the standard and PRO plugins\non:\n  release:\n    types: [published]\n\njobs:\n  process:\n    name: Generate plugins\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions\/checkout@v3\n\n      - name: Install zip\n        uses: montudor\/action-zip@v1.0.0\n\n      - name: Create the standard plugin .zip file (excluding all PRO code)\n        run: zip -X -r myplugin-standard.zip . -x **\/src\/\\pro\/\\*\n\n      - name: Create the PRO plugin .zip file\n        run: zip -X -r myplugin-pro.zip . -x myplugin-standard.zip\n\n      - name: Upload both plugins to the release page\n        uses: softprops\/action-gh-release@v1\n        with:\n          files: |\n            myplugin-standard.zip\n            myplugin-pro.zip\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n<\/code><\/pre>\n<h2>Problemen met de &#8220;PRO die de gratis versie vervangt&#8221; strategie<\/h2>\n<p>De &#8220;PRO die de gratis versie vervangt&#8221; strategie vereist het vervangen van de gratis plugin door de PRO versie. Als gevolg daarvan, als de gratis plugin wordt gedistribueerd via de WordPress plugin directory, zal het aantal &#8220;actieve installaties&#8221; dalen (omdat alleen de gratis plugin wordt bijgehouden, niet de PRO versie), waardoor de indruk wordt gewekt dat de plugin minder populair is dan hij in werkelijkheid is.<\/p>\n<p>Dit zou het doel van het gebruik van de WordPress plugin directory teniet doen: Als een ontdekkingskanaal van plugins waar gebruikers onze plugin kunnen vinden, downloaden en installeren. (Daarna, als het eenmaal ge\u00efnstalleerd is, kan de gratis plugin gebruikers uitnodigen om te upgraden naar de PRO versie).<\/p>\n<p>Als het aantal actieve installaties niet hoog is, worden gebruikers wellicht niet overtuigd om onze plugin te installeren. Een voorbeeld van hoe het mis kan gaan: de eigenaren van de Newsletter Glue plugin besloten om <a href=\"https:\/\/wptavern.com\/newsletter-glue-closes-free-plugin-on-wordpress-org\" target=\"_blank\" rel=\"noopener noreferrer\">de plugin te verwijderen uit de WordPress plugin directory<\/a>, omdat het lage aantal actieve installaties de vooruitzichten van de plugin schaadde.<\/p>\n<p>Omdat de &#8220;PRO die de gratis versie vervangt&#8221; strategie niet levensvatbaar is, blijft er maar \u00e9\u00e9n keuze over: de &#8220;PRO bovenop gratis&#8221; strategie.<\/p>\n<p>Laten we de ins en outs van deze strategie bekijken.<\/p>\n<h2>Conceptualisering van de &#8220;PRO naast gratis&#8221; strategie<\/h2>\n<p>Het idee is dat de gratis plugin op de site wordt ge\u00efnstalleerd en dat de functionaliteit kan worden uitgebreid door extra plugins of addons te installeren. Dit kan via een enkele PRO plugin of via een verzameling PRO extensies of addons, waarbij elk van hen een specifieke functionaliteit biedt.<\/p>\n<p>In deze situatie maakt het de gratis plugin niet uit welke andere plugins er op de site ge\u00efnstalleerd zijn. Het enige wat het doet is extra functionaliteit bieden. Dit model is veelzijdig en biedt ruimte voor uitbreiding door zowel de oorspronkelijke developers als externe makers, waardoor een ecosysteem ontstaat waarin een plugin zich in onvoorziene richtingen kan ontwikkelen.<\/p>\n<p>Merk op dat het niet uitmaakt of de PRO uitbreidingen door ons geproduceerd worden (d.w.z. dezelfde developers die de standaard plugin bouwen), of door iemand anders: De code om met beide om te gaan is hetzelfde. Daarom is het een goed idee om een basis te cre\u00ebren die geen beperkingen oplegt aan hoe de plugin kan worden uitgebreid. Dit maakt het mogelijk voor externe d om onevelopers ze plugin uit te breiden op manieren waar wij niet aan gedacht hadden.<\/p>\n<h2>Ontwerpbenaderingen: hooks en service containers<\/h2>\n<p>Er zijn twee hoofdbenaderingen om PHP code uitbreidbaar te maken:<\/p>\n<ol>\n<li>Via de WordPress <a href=\"https:\/\/kinsta.com\/nl\/blog\/wordpress-hooks\/\">action- en filterhooks<\/a><\/li>\n<li>Via een <a href=\"https:\/\/kinsta.com\/nl\/blog\/wordpress-abstractieplugins\/#accessing-services-via-the-service-container\">service container<\/a><\/li>\n<\/ol>\n<p>De eerste benadering is het meest gebruikelijk onder WordPress developers, terwijl de laatste de voorkeur heeft van de bredere <a href=\"https:\/\/kinsta.com\/php\/\">PHP community<\/a>.<\/p>\n<p>Laten we voorbeelden van beide bekijken.<\/p>\n<h2>Code uitbreidbaar maken via action- en filterhooks<\/h2>\n<p>WordPress biedt <a href=\"https:\/\/developer.wordpress.org\/plugins\/hooks\/\" target=\"_blank\" rel=\"noopener noreferrer\">hooks<\/a> (filters en actions) als een mechanisme om gedrag aan te passen. Filterhooks worden gebruikt om waarden te overschrijven en actionhooks om aangepaste functionaliteit uit te voeren.<\/p>\n<p>Onze hoofdplugin kan dan worden &#8220;bezaaid&#8221; met hooks in de hele codebase, zodat developers het gedrag kunnen aanpassen.<\/p>\n<p>Een goed voorbeeld hiervan is <a href=\"https:\/\/kinsta.com\/nl\/blog\/woocommerce-handleiding\/\">WooCommerce<\/a>, dat een enorm ecosysteem van add-ons heeft, waarvan de meerderheid eigendom is van derde partijen. Dit is mogelijk dankzij het <a href=\"https:\/\/woocommerce.github.io\/code-reference\/hooks\/hooks.html\" target=\"_blank\" rel=\"noopener noreferrer\">uitgebreide aantal hooks<\/a> dat deze plugin biedt.<\/p>\n<p>De devs van WooCommerce hebben doelbewust hooks toegevoegd, ook al hebben ze die zelf niet nodig. Iemand anders kan ze gebruiken. Let op het grote aantal &#8220;after&#8221; en &#8220;before&#8221; actionhooks:<\/p>\n<ul>\n<li><code>woocommerce_after_account_downloads<\/code><\/li>\n<li><code>woocommerce_after_account_navigation<\/code><\/li>\n<li><code>woocommerce_after_account_orders<\/code><\/li>\n<li><code>woocommerce_after_account_payment_methods<\/code><\/li>\n<li><code>woocommerce_after_available_downloads<\/code><\/li>\n<li><code>woocommerce_after_cart<\/code><\/li>\n<li><code>woocommerce_after_cart_contents<\/code><\/li>\n<li><code>woocommerce_after_cart_item_name<\/code><\/li>\n<li><code>woocommerce_after_cart_table<\/code><\/li>\n<li><code>woocommerce_after_cart_totals<\/code><\/li>\n<li>&#8230;<\/li>\n<li><code>woocommerce_before_account_downloads<\/code><\/li>\n<li><code>woocommerce_before_account_navigation<\/code><\/li>\n<li><code>woocommerce_before_account_orders<\/code><\/li>\n<li><code>woocommerce_before_account_orders_pagination<\/code><\/li>\n<li><code>woocommerce_before_account_payment_methods<\/code><\/li>\n<li><code>woocommerce_before_available_downloads<\/code><\/li>\n<li><code>woocommerce_before_cart<\/code><\/li>\n<li><code>woocommerce_before_cart_collaterals<\/code><\/li>\n<li><code>woocommerce_before_cart_contents<\/code><\/li>\n<li><code>woocommerce_before_cart_table<\/code><\/li>\n<li><code>woocommerce_before_cart_totals<\/code><\/li>\n<li>&#8230;<\/li>\n<\/ul>\n<p>Als voorbeeld bevat bestand <a href=\"https:\/\/woocommerce.github.io\/code-reference\/files\/woocommerce-templates-myaccount-downloads.html#source-view.27\" target=\"_blank\" rel=\"noopener noreferrer\"><code>downloads.php<\/code><\/a> verschillende acties om extra functionaliteit te injecteren, en de URL van de winkel kan worden overschreven via een filter:<\/p>\n<pre><code class=\"language-php\">&lt;?php\n\n$downloads     = WC()-&gt;customer-&gt;get_downloadable_products();\n$has_downloads = (bool) $downloads;\n\ndo_action( 'woocommerce_before_account_downloads', $has_downloads ); ?&gt;\n\n&lt;?php if ( $has_downloads ) : ?&gt;\n\n  &lt;?php do_action( 'woocommerce_before_available_downloads' ); ?&gt;\n\n  &lt;?php do_action( 'woocommerce_available_downloads', $downloads ); ?&gt;\n\n  &lt;?php do_action( 'woocommerce_after_available_downloads' ); ?&gt;\n\n&lt;?php else : ?&gt;\n\n  &lt;?php\n\n  $wp_button_class = wc_wp_theme_get_element_class_name( 'button' ) ? ' ' . wc_wp_theme_get_element_class_name( 'button' ) : '';\n  wc_print_notice( esc_html__( 'No downloads available yet.', 'woocommerce' ) . ' &lt;a class=\"button wc-forward' . esc_attr( $wp_button_class ) . '\" href=\"' . esc_url( apply_filters( 'woocommerce_return_to_shop_redirect', wc_get_page_permalink( 'shop' ) ) ) . '\"&gt;' . esc_html__( 'Browse products', 'woocommerce' ) . '&lt;\/a&gt;', 'notice' );\n  ?&gt;\n\n&lt;?php endif; ?&gt;\n\n&lt;?php do_action( 'woocommerce_after_account_downloads', $has_downloads ); ?&gt;\n<\/code><\/pre>\n<h2>Code uitbreidbaar maken via service containers<\/h2>\n<p>Een servicecontainer is een PHP object dat ons helpt bij het beheren van de instanti\u00ebring van alle klassen in het project, meestal aangeboden als onderdeel van een &#8220;dependency injection&#8221; bibliotheek.<\/p>\n<p><a href=\"https:\/\/kinsta.com\/nl\/blog\/wordpress-abstractieplugins\/#using-dependency-injection\">Dependency injection<\/a> is een strategie die het mogelijk maakt om alle onderdelen van de applicatie op een gedecentraliseerde manier aan elkaar te lijmen: PHP klassen worden in de applicatie ge\u00efnjecteerd via configuratie en de applicatie haalt instanties van deze PHP klassen op via de service container.<\/p>\n<p>Er zijn veel bibliotheken voor het injecteren van dependencies die je kan gebruiken. De volgende zijn populair en uitwisselbaar, omdat ze allemaal voldoen aan <a href=\"https:\/\/www.php-fig.org\/psr\/psr-11\/\" target=\"_blank\" rel=\"noopener noreferrer\">PSR-11<\/a> (PHP standaard aanbeveling) dat dependency-injectie containers beschrijft:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/symfony\/dependency-injection\" target=\"_blank\" rel=\"noopener noreferrer\">Symfony&#8217;s DependencyInjection<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/PHP-DI\/PHP-DI\" target=\"_blank\" rel=\"noopener noreferrer\">PHP-DI<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/silexphp\/Pimple\" target=\"_blank\" rel=\"noopener noreferrer\">Pimple<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/thephpleague\/container\" target=\"_blank\" rel=\"noopener noreferrer\">Container (dependency-injection)<\/a><\/li>\n<\/ul>\n<p><a href=\"https:\/\/kinsta.com\/nl\/blog\/wat-is-laravel\/\">Laravel<\/a> bevat ook een <a href=\"https:\/\/laravel.com\/docs\/10.x\/container\" target=\"_blank\" rel=\"noopener noreferrer\">service container<\/a> die al in de applicatie is ingebakken.<\/p>\n<p>Met behulp van dependency injection hoeft de gratis plugin niet van tevoren te weten welke PHP-klassen aanwezig zijn tijdens runtime: Hij vraagt simpelweg instanties van alle klassen op bij de service container. Hoewel veel PHP klassen worden geleverd door de gratis plugin zelf om aan de functionaliteit te voldoen, worden andere geleverd door de add-ons die op de site zijn ge\u00efnstalleerd om de functionaliteit uit te breiden.<\/p>\n<p>Een goed voorbeeld van het gebruik van een servicecontainer is <a href=\"https:\/\/wordpress.org\/plugins\/gatographql\/\" target=\"_blank\" rel=\"noopener noreferrer\">Gato GraphQL<\/a>, dat vertrouwt op de DependencyInjection bibliotheek van Symfony.<\/p>\n<p>Dit is <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/ce461a7155782823b9d14383e1036a75b564807c\/layers\/Engine\/packages\/root\/src\/Container\/ContainerBuilderFactoryTrait.php\" target=\"_blank\" rel=\"noopener noreferrer\">hoe de service container wordt ge\u00efnstantieerd<\/a>:<\/p>\n<pre><code class=\"language-php\">&lt;?php\n\ndeclare(strict_types=1);\n\nnamespace GatoGraphQL\\Container;\n\nuse Symfony\\Component\\Config\\ConfigCache;\nuse Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface;\nuse Symfony\\Component\\DependencyInjection\\Dumper\\PhpDumper;\n\ntrait ContainerBuilderFactoryTrait\n{\n  protected ContainerInterface $instance;\n  protected bool $cacheContainerConfiguration;\n  protected bool $cached;\n  protected string $cacheFile;\n\n  \/**\n   * Initialize the Container Builder.\n   * If the directory is not provided, store the\n   * cache in a system temp dir\n   *\/\n  public function init(\n    bool $cacheContainerConfiguration,\n    string $namespace,\n    string $directory\n  ): void {\n    $this-&gt;cacheContainerConfiguration = $cacheContainerConfiguration;\n\n    if ($this-&gt;cacheContainerConfiguration) {\n      if (!$directory) {\n        $directory = sys_get_temp_dir() . \\DIRECTORY_SEPARATOR . 'container-cache';\n      }\n      $directory .= \\DIRECTORY_SEPARATOR . $namespace;\n      if (!is_dir($directory)) {\n        @mkdir($directory, 0777, true);\n      }\n      \n      \/\/ Store the cache under this file\n      $this-&gt;cacheFile = $directory . 'container.php';\n\n      $containerConfigCache = new ConfigCache($this-&gt;cacheFile, false);\n      $this-&gt;cached = $containerConfigCache-&gt;isFresh();\n    } else {\n      $this-&gt;cached = false;\n    }\n\n    \/\/ If not cached, then create the new instance\n    if (!$this-&gt;cached) {\n      $this-&gt;instance = new ContainerBuilder();\n    } else {\n      require_once $this-&gt;cacheFile;\n      \/** @var class-string&lt;ContainerBuilder&gt; *\/\n      $containerFullyQuantifiedClass = \"\\\\GatoGraphQL\\\\ServiceContainer\";\n      $this-&gt;instance = new $containerFullyQuantifiedClass();\n    }\n  }\n\n  public function getInstance(): ContainerInterface\n  {\n    return $this-&gt;instance;\n  }\n\n  \/**\n   * If the container is not cached, then compile it and cache it\n   *\n   * @param CompilerPassInterface[] $compilerPasses Compiler Pass objects to register on the container\n   *\/\n  public function maybeCompileAndCacheContainer(\n    array $compilerPasses = []\n  ): void {\n    \/**\n     * Compile Symfony's DependencyInjection Container Builder.\n     *\n     * After compiling, cache it in disk for performance.\n     *\n     * This happens only the first time the site is accessed\n     * on the current server.\n     *\/\n    if ($this-&gt;cached) {\n      return;\n    }\n\n    \/** @var ContainerBuilder *\/\n    $containerBuilder = $this-&gt;getInstance();\n    foreach ($compilerPasses as $compilerPass) {\n      $containerBuilder-&gt;addCompilerPass($compilerPass);\n    }\n\n    \/\/ Compile the container.\n    $containerBuilder-&gt;compile();\n\n    \/\/ Cache the container\n    if (!$this-&gt;cacheContainerConfiguration) {\n      return;\n    }\n    \n    \/\/ Create the folder if it doesn't exist, and check it was successful\n    $dir = dirname($this-&gt;cacheFile);\n    $folderExists = file_exists($dir);\n    if (!$folderExists) {\n      $folderExists = @mkdir($dir, 0777, true);\n      if (!$folderExists) {\n        return;\n      }\n    }\n\n    \/\/ Save the container to disk\n    $dumper = new PhpDumper($containerBuilder);\n    file_put_contents(\n      $this-&gt;cacheFile,\n      $dumper-&gt;dump(\n        [\n          'class' =&gt; 'ServiceContainer',\n          'namespace' =&gt; 'GatoGraphQL',\n        ]\n      )\n    );\n\n    \/\/ Change the permissions so it can be modified by external processes\n    chmod($this-&gt;cacheFile, 0777);\n  }\n}\n<\/code><\/pre>\n<p>Merk op dat de service container (toegankelijk onder PHP-object met klasse <code>GatoGraphQL\\ServiceContainer<\/code>) wordt gegenereerd de eerste keer dat de plugin wordt uitgevoerd en vervolgens op schijf wordt gecached (als bestand <code>container.php<\/code> in een temp-map van het systeem). Dit komt omdat het genereren van de service container een zwaar proces is dat mogelijk enkele seconden kan duren.<\/p>\n<p>Vervolgens <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/fd73bf65cd30e164ffaa7860861974cf53683d51\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/config\/services.yaml\" target=\"_blank\" rel=\"noopener noreferrer\">defini\u00ebren<\/a> zowel de hoofdplugin als al zijn uitbreidingen <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/fd73bf65cd30e164ffaa7860861974cf53683d51\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/config\/services.yaml\" target=\"_blank\" rel=\"noopener noreferrer\">via een configuratiebestand welke services ze in de container willen injecteren<\/a>:<\/p>\n<pre><code class=\"language-yaml\">services:\n  _defaults:\n    public: true\n    autowire: true\n    autoconfigure: true\n\n  GatoGraphQL\\GatoGraphQL\\Registries\\ModuleTypeRegistryInterface:\n    class: \\GatoGraphQL\\GatoGraphQL\\Registries\\ModuleTypeRegistry\n\n  GatoGraphQL\\GatoGraphQL\\Log\\LoggerInterface:\n    class: \\GatoGraphQL\\GatoGraphQL\\Log\\Logger\n\n  GatoGraphQL\\GatoGraphQL\\Services\\:\n    resource: ..\/src\/Services\/*\n\n  GatoGraphQL\\GatoGraphQL\\State\\:\n    resource: '..\/src\/State\/*'\n\n<\/code><\/pre>\n<p>Merk op dat we objecten kunnen instanti\u00ebren voor specifieke klassen (zoals <code>GatoGraphQL\\GatoGraphQL\\Log\\Logger<\/code>, benaderd via zijn contract interface <code>GatoGraphQL\\GatoGraphQL\\Log\\LoggerInterface<\/code>), en we kunnen ook aangeven &#8220;alle klassen onder een bepaalde map instanti\u00ebren&#8221; (zoals alle diensten onder <code>..\/src\/Services<\/code>).<\/p>\n<p>Tenslotte <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/409eb230267021735e3326657c9929e94fa08b85\/layers\/Engine\/packages\/root\/src\/Module\/InitializeContainerServicesInModuleTrait.php\" target=\"_blank\" rel=\"noopener noreferrer\">injecteren we de configuratie in de service container<\/a>:<\/p>\n<pre><code class=\"language-php\">&lt;?php\n\ndeclare(strict_types=1);\n\nnamespace PoP\\Root\\Module;\n\nuse PoP\\Root\\App;\nuse Symfony\\Component\\Config\\FileLocator;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\nuse Symfony\\Component\\DependencyInjection\\Loader\\YamlFileLoader;\n\ntrait InitializeContainerServicesInModuleTrait\n{\n  \/\/ Initialize the services defined in the YAML configuration file.\n  public function initServices(\n    string $dir,\n    string $serviceContainerConfigFileName\n  ): void {\n    \/\/ First check if the container has been cached. If so, do nothing\n    if (App::getContainerBuilderFactory()-&gt;isCached()) {\n      return;\n    }\n\n    \/\/ Initialize the ContainerBuilder with this module's service implementations\n    \/** @var ContainerBuilder *\/\n    $containerBuilder = App::getContainer();\n    $loader = new YamlFileLoader($containerBuilder, new FileLocator($dir));\n    $loader-&gt;load($serviceContainerConfigFileName);\n  }\n}\n<\/code><\/pre>\n<p>Services die in de container worden ge\u00efnjecteerd kunnen worden geconfigureerd om altijd te worden ge\u00efnitialiseerd of alleen wanneer daarom wordt gevraagd (lazy mode).<\/p>\n<p>Om bijvoorbeeld een custom berichttype te representeren, heeft de plugin de klasse <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/04058ffae6700e3e34e529ebd709247d98106fed\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/src\/Services\/CustomPostTypes\/AbstractCustomPostType.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>AbstractCustomPostType<\/code><\/a>waarvan de methode <code>initialize<\/code> de logica uitvoert om het te initialiseren volgens WordPress:<\/p>\n<pre><code class=\"language-php\">&lt;?php\n\ndeclare(strict_types=1);\n\nnamespace GatoGraphQL\\GatoGraphQL\\Services\\CustomPostTypes;\n\nuse GatoGraphQL\\GatoGraphQL\\Services\\Taxonomies\\TaxonomyInterface;\nuse PoP\\Root\\Services\\AbstractAutomaticallyInstantiatedService;\n\nabstract class AbstractCustomPostType extends AbstractAutomaticallyInstantiatedService implements CustomPostTypeInterface\n{\n  public function initialize(): void\n  {\n    \\add_action(\n      'init',\n      $this-&gt;initCustomPostType(...)\n    );\n  }\n\n  \/**\n   * Register the post type\n   *\/\n  public function initCustomPostType(): void\n  {\n    \\register_post_type($this-&gt;getCustomPostType(), $this-&gt;getCustomPostTypeArgs());\n  }\n\n  abstract public function getCustomPostType(): string;\n\n  \/**\n   * Arguments for registering the post type\n   *\n   * @return array&lt;string,mixed&gt;\n   *\/\n  protected function getCustomPostTypeArgs(): array\n  {\n    \/** @var array&lt;string,mixed&gt; *\/\n    $postTypeArgs = [\n      'public' =&gt; $this-&gt;isPublic(),\n      'publicly_queryable' =&gt; $this-&gt;isPubliclyQueryable(),\n      'label' =&gt; $this-&gt;getCustomPostTypeName(),\n      'labels' =&gt; $this-&gt;getCustomPostTypeLabels($this-&gt;getCustomPostTypeName(), $this-&gt;getCustomPostTypePluralNames(true), $this-&gt;getCustomPostTypePluralNames(false)),\n      'capability_type' =&gt; 'post',\n      'hierarchical' =&gt; $this-&gt;isAPIHierarchyModuleEnabled() && $this-&gt;isHierarchical(),\n      'exclude_from_search' =&gt; true,\n      'show_in_admin_bar' =&gt; $this-&gt;showInAdminBar(),\n      'show_in_nav_menus' =&gt; true,\n      'show_ui' =&gt; true,\n      'show_in_menu' =&gt; true,\n      'show_in_rest' =&gt; true,\n    ];\n    return $postTypeArgs;\n  }\n\n  \/**\n   * Labels for registering the post type\n   *\n   * @param string $name_uc Singular name uppercase\n   * @param string $names_uc Plural name uppercase\n   * @param string $names_lc Plural name lowercase\n   * @return array&lt;string,string&gt;\n   *\/\n  protected function getCustomPostTypeLabels(string $name_uc, string $names_uc, string $names_lc): array\n  {\n    return array(\n      'name'         =&gt; $names_uc,\n      'singular_name'    =&gt; $name_uc,\n      'add_new'      =&gt; sprintf(\\__('Add New %s', 'gatographql'), $name_uc),\n      'add_new_item'     =&gt; sprintf(\\__('Add New %s', 'gatographql'), $name_uc),\n      'edit_item'      =&gt; sprintf(\\__('Edit %s', 'gatographql'), $name_uc),\n      'new_item'       =&gt; sprintf(\\__('New %s', 'gatographql'), $name_uc),\n      'all_items'      =&gt; $names_uc,\/\/sprintf(\\__('All %s', 'gatographql'), $names_uc),\n      'view_item'      =&gt; sprintf(\\__('View %s', 'gatographql'), $name_uc),\n      'search_items'     =&gt; sprintf(\\__('Search %s', 'gatographql'), $names_uc),\n      'not_found'      =&gt; sprintf(\\__('No %s found', 'gatographql'), $names_lc),\n      'not_found_in_trash' =&gt; sprintf(\\__('No %s found in Trash', 'gatographql'), $names_lc),\n      'parent_item_colon'  =&gt; sprintf(\\__('Parent %s:', 'gatographql'), $name_uc),\n    );\n  }\n}\n<\/code><\/pre>\n<p>Dan is de klasse <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/c870d8906ae1aec3c81acc039c53acc7aab5dff0\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/src\/Services\/CustomPostTypes\/GraphQLCustomEndpointCustomPostType.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>GraphQLCustomEndpointCustomPostType.php<\/code><\/a> een implementatie van een custom berichttype. Nadat het als service in de container is ge\u00efnjecteerd, wordt het ge\u00efnstantieerd en geregistreerd in WordPress:<\/p>\n<pre><code class=\"language-php\">&lt;?php\n\ndeclare(strict_types=1);\n\nnamespace GatoGraphQL\\GatoGraphQL\\Services\\CustomPostTypes;\n\nclass GraphQLCustomEndpointCustomPostType extends AbstractCustomPostType\n{\n  public function getCustomPostType(): string\n  {\n    return 'graphql-endpoint';\n  }\n\n  protected function getCustomPostTypeName(): string\n  {\n    return \\__('GraphQL custom endpoint', 'gatographql');\n  }\n}\n<\/code><\/pre>\n<p>Deze klasse is aanwezig in de gratis plugin en andere aangepaste posttype klassen, vergelijkbaar met uitbreidingen van <code>AbstractCustomPostType<\/code>, worden geleverd door PRO extensies.<\/p>\n<h2>Vergelijking hooks en service containers<\/h2>\n<p>Laten we de twee ontwerpbenaderingen eens vergelijken.<\/p>\n<p>Aan de positieve kant is het voor action- en filterhooks de eenvoudigere methode, omdat de functionaliteit deel uitmaakt van de WordPress core. En elke developer die met WordPress werkt, weet al hoe hij hooks moet gebruiken, dus de leercurve is laag.<\/p>\n<p>De logica is echter gekoppeld aan een hooknaam, wat een string is, en als zodanig kan leiden tot bugs: Als de hooknaam wordt gewijzigd, wordt de logica van de extensie verbroken. Het is echter mogelijk dat de developer niet merkt dat er een probleem is, omdat de PHP code nog steeds compileert.<\/p>\n<p>Als gevolg daarvan hebben afgeschreven hooks de neiging om heel lang in de codebase te blijven, mogelijk zelfs voor altijd. Het project verzamelt dan afgezaagde code die niet kan worden verwijderd uit angst om extensies kapot te maken.<\/p>\n<p>Terug naar WooCommerce, deze situatie blijkt uit het bestand <a href=\"https:\/\/woocommerce.github.io\/code-reference\/files\/woocommerce-templates-myaccount-dashboard.html#source-view.65\" target=\"_blank\" rel=\"noopener noreferrer\"><code>dashboard.php<\/code><\/a> (merk op hoe de afgeschreven hooks worden bewaard sinds versie <code>2.6<\/code>, terwijl de huidige laatste versie <code>8.5<\/code> is):<\/p>\n<pre><code class=\"language-php\">&lt;?php\n  \/**\n   * My Account dashboard.\n   *\n   * @since 2.6.0\n   *\/\n  do_action( 'woocommerce_account_dashboard' );\n\n  \/**\n   * Deprecated woocommerce_before_my_account action.\n   *\n   * @deprecated 2.6.0\n   *\/\n  do_action( 'woocommerce_before_my_account' );\n\n  \/**\n   * Deprecated woocommerce_after_my_account action.\n   *\n   * @deprecated 2.6.0\n   *\/\n  do_action( 'woocommerce_after_my_account' );\n<\/code><\/pre>\n<p>Het gebruik van een service container heeft als nadeel dat er een externe bibliotheek nodig is, wat de complexiteit nog verder vergroot. Bovendien <a href=\"https:\/\/gatographql.com\/blog\/graphql-api-for-wp-is-now-scoped-thanks-to-php-scoper\/#heading-php-scoper-the-easy-way-%F0%9F%98%8E\" target=\"_blank\" rel=\"noopener noreferrer\">moet deze bibliotheek worden gescoped<\/a> (met <a href=\"https:\/\/github.com\/humbug\/php-scoper\" target=\"_blank\" rel=\"noopener noreferrer\">PHP-Scoper<\/a> of <a href=\"https:\/\/github.com\/BrianHenryIE\/strauss\" target=\"_blank\" rel=\"noopener noreferrer\">Strauss<\/a>) uit angst dat een andere versie van dezelfde bibliotheek wordt ge\u00efnstalleerd door een andere plugin op dezelfde site, wat conflicten kan opleveren.<\/p>\n<p>Het gebruik van een service container is ongetwijfeld moeilijker te implementeren en kost meer ontwikkeltijd.<\/p>\n<p>Aan de andere kant hebben service containers te maken met PHP klassen zonder dat je logica hoeft te koppelen aan een of andere string. Dit zorgt ervoor dat het project meer <a href=\"https:\/\/phptherightway.com\/\" target=\"_blank\" rel=\"noopener noreferrer\">PHP best practices<\/a> gebruikt, wat leidt tot een codebase die op de lange termijn gemakkelijker te onderhouden is.<\/p>\n<h2>Samenvatting<\/h2>\n<p>Wanneer je een plugin voor WordPress maakt, is het een goed idee om extensies te ondersteunen, zodat wij (de makers van de plugin) commerci\u00eble functies kunnen aanbieden en ook anderen extra functionaliteit kunnen toevoegen en hopelijk een ecosysteem rond de plugin kunnen opbouwen.<\/p>\n<p>In dit artikel hebben we onderzocht wat de overwegingen zijn met betrekking tot de architectuur van het PHP project om de plugin uitbreidbaar te maken. Zoals we hebben geleerd, kunnen we kiezen tussen twee ontwerpbenaderingen: hooks gebruiken of een service container gebruiken. En we hebben beide benaderingen vergeleken en de deugden en zwakheden van beide ge\u00efdentificeerd.<\/p>\n<p><em>Ben jij van plan om je WordPress plugin uitbreidbaar te maken? Laat het ons weten in de commentsectie.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In het WordPress ecosysteem is het gebruik van een freemium model een veelgebruikte methode voor het promoten en te gelde maken van commerci\u00eble plugins. Deze aanpak &#8230;<\/p>\n","protected":false},"author":196,"featured_media":58653,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_kinsta_gated_content":false,"_kinsta_gated_content_redirect":"","footnotes":""},"tags":[],"topic":[892,899],"class_list":["post-58652","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","topic-wordpress-ontwikkeling","topic-wordpress-plugins"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v24.6 (Yoast SEO v24.6) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Zo maak je een WordPress plugin maken die uitbreidingen ondersteunt - Kinsta\u00ae<\/title>\n<meta name=\"description\" content=\"Leer hoe je een flexibele, schaalbare plugin maakt die uitbreidingen ondersteunt, functionaliteit verbetert en compatibiliteit maximaliseert.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/\" \/>\n<meta property=\"og:locale\" content=\"nl_NL\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Zo bouw je een WordPress plugin die extensies ondersteunt\" \/>\n<meta property=\"og:description\" content=\"Leer hoe je een flexibele, schaalbare plugin maakt die uitbreidingen ondersteunt, functionaliteit verbetert en compatibiliteit maximaliseert.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/\" \/>\n<meta property=\"og:site_name\" content=\"Kinsta\u00ae\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/Kinsta-Nederland-476213452787823\/\" \/>\n<meta property=\"article:published_time\" content=\"2024-03-04T13:14:30+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-03-12T17:30:28+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1460\" \/>\n\t<meta property=\"og:image:height\" content=\"730\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Leonardo Losoviz\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:description\" content=\"Leer hoe je een flexibele, schaalbare plugin maakt die uitbreidingen ondersteunt, functionaliteit verbetert en compatibiliteit maximaliseert.\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg\" \/>\n<meta name=\"twitter:creator\" content=\"@losoviz\" \/>\n<meta name=\"twitter:site\" content=\"@Kinsta_NL\" \/>\n<meta name=\"twitter:label1\" content=\"Geschreven door\" \/>\n\t<meta name=\"twitter:data1\" content=\"Leonardo Losoviz\" \/>\n\t<meta name=\"twitter:label2\" content=\"Geschatte leestijd\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minuten\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/\"},\"author\":{\"name\":\"Leonardo Losoviz\",\"@id\":\"https:\/\/kinsta.com\/nl\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238\"},\"headline\":\"Zo bouw je een WordPress plugin die extensies ondersteunt\",\"datePublished\":\"2024-03-04T13:14:30+00:00\",\"dateModified\":\"2024-03-12T17:30:28+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/\"},\"wordCount\":1888,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/kinsta.com\/nl\/#organization\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg\",\"inLanguage\":\"nl-NL\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/\",\"url\":\"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/\",\"name\":\"Zo maak je een WordPress plugin maken die uitbreidingen ondersteunt - Kinsta\u00ae\",\"isPartOf\":{\"@id\":\"https:\/\/kinsta.com\/nl\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg\",\"datePublished\":\"2024-03-04T13:14:30+00:00\",\"dateModified\":\"2024-03-12T17:30:28+00:00\",\"description\":\"Leer hoe je een flexibele, schaalbare plugin maakt die uitbreidingen ondersteunt, functionaliteit verbetert en compatibiliteit maximaliseert.\",\"breadcrumb\":{\"@id\":\"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#breadcrumb\"},\"inLanguage\":\"nl-NL\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"nl-NL\",\"@id\":\"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#primaryimage\",\"url\":\"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg\",\"contentUrl\":\"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg\",\"width\":1460,\"height\":730},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/kinsta.com\/nl\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"WordPress plugins\",\"item\":\"https:\/\/kinsta.com\/nl\/onderwerpen\/wordpress-plugins\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Zo bouw je een WordPress plugin die extensies ondersteunt\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/kinsta.com\/nl\/#website\",\"url\":\"https:\/\/kinsta.com\/nl\/\",\"name\":\"Kinsta\u00ae\",\"description\":\"Snelle, veilige, premium hostingoplossingen\",\"publisher\":{\"@id\":\"https:\/\/kinsta.com\/nl\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/kinsta.com\/nl\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"nl-NL\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/kinsta.com\/nl\/#organization\",\"name\":\"Kinsta\",\"url\":\"https:\/\/kinsta.com\/nl\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"nl-NL\",\"@id\":\"https:\/\/kinsta.com\/nl\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2023\/12\/kinsta-logo.jpeg\",\"contentUrl\":\"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2023\/12\/kinsta-logo.jpeg\",\"width\":500,\"height\":500,\"caption\":\"Kinsta\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/nl\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/Kinsta-Nederland-476213452787823\/\",\"https:\/\/x.com\/Kinsta_NL\",\"https:\/\/www.instagram.com\/kinstahosting\/\",\"https:\/\/www.linkedin.com\/company\/kinsta\/\",\"https:\/\/www.pinterest.com\/kinstahosting\/\",\"https:\/\/www.youtube.com\/c\/Kinsta\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/kinsta.com\/nl\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238\",\"name\":\"Leonardo Losoviz\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"nl-NL\",\"@id\":\"https:\/\/kinsta.com\/nl\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/b28085726ee66e49f08be16ad668efd5?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/b28085726ee66e49f08be16ad668efd5?s=96&d=mm&r=g\",\"caption\":\"Leonardo Losoviz\"},\"description\":\"Leo writes about innovative web development trends, mostly concerning PHP, WordPress and GraphQL. You can find him at leoloso.com and twitter.com\/losoviz.\",\"sameAs\":[\"https:\/\/leoloso.com\",\"https:\/\/x.com\/losoviz\",\"https:\/\/www.youtube.com\/@GatoGraphQL\"],\"url\":\"https:\/\/kinsta.com\/nl\/blog\/author\/leonardolosoviz\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Zo maak je een WordPress plugin maken die uitbreidingen ondersteunt - Kinsta\u00ae","description":"Leer hoe je een flexibele, schaalbare plugin maakt die uitbreidingen ondersteunt, functionaliteit verbetert en compatibiliteit maximaliseert.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/","og_locale":"nl_NL","og_type":"article","og_title":"Zo bouw je een WordPress plugin die extensies ondersteunt","og_description":"Leer hoe je een flexibele, schaalbare plugin maakt die uitbreidingen ondersteunt, functionaliteit verbetert en compatibiliteit maximaliseert.","og_url":"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/","og_site_name":"Kinsta\u00ae","article_publisher":"https:\/\/www.facebook.com\/Kinsta-Nederland-476213452787823\/","article_published_time":"2024-03-04T13:14:30+00:00","article_modified_time":"2024-03-12T17:30:28+00:00","og_image":[{"width":1460,"height":730,"url":"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg","type":"image\/jpeg"}],"author":"Leonardo Losoviz","twitter_card":"summary_large_image","twitter_description":"Leer hoe je een flexibele, schaalbare plugin maakt die uitbreidingen ondersteunt, functionaliteit verbetert en compatibiliteit maximaliseert.","twitter_image":"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg","twitter_creator":"@losoviz","twitter_site":"@Kinsta_NL","twitter_misc":{"Geschreven door":"Leonardo Losoviz","Geschatte leestijd":"13 minuten"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#article","isPartOf":{"@id":"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/"},"author":{"name":"Leonardo Losoviz","@id":"https:\/\/kinsta.com\/nl\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238"},"headline":"Zo bouw je een WordPress plugin die extensies ondersteunt","datePublished":"2024-03-04T13:14:30+00:00","dateModified":"2024-03-12T17:30:28+00:00","mainEntityOfPage":{"@id":"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/"},"wordCount":1888,"commentCount":0,"publisher":{"@id":"https:\/\/kinsta.com\/nl\/#organization"},"image":{"@id":"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#primaryimage"},"thumbnailUrl":"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg","inLanguage":"nl-NL","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/","url":"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/","name":"Zo maak je een WordPress plugin maken die uitbreidingen ondersteunt - Kinsta\u00ae","isPartOf":{"@id":"https:\/\/kinsta.com\/nl\/#website"},"primaryImageOfPage":{"@id":"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#primaryimage"},"image":{"@id":"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#primaryimage"},"thumbnailUrl":"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg","datePublished":"2024-03-04T13:14:30+00:00","dateModified":"2024-03-12T17:30:28+00:00","description":"Leer hoe je een flexibele, schaalbare plugin maakt die uitbreidingen ondersteunt, functionaliteit verbetert en compatibiliteit maximaliseert.","breadcrumb":{"@id":"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#breadcrumb"},"inLanguage":"nl-NL","potentialAction":[{"@type":"ReadAction","target":["https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/"]}]},{"@type":"ImageObject","inLanguage":"nl-NL","@id":"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#primaryimage","url":"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg","contentUrl":"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg","width":1460,"height":730},{"@type":"BreadcrumbList","@id":"https:\/\/kinsta.com\/nl\/blog\/pro-gratis-versies-wordpress-plugin\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/kinsta.com\/nl\/"},{"@type":"ListItem","position":2,"name":"WordPress plugins","item":"https:\/\/kinsta.com\/nl\/onderwerpen\/wordpress-plugins\/"},{"@type":"ListItem","position":3,"name":"Zo bouw je een WordPress plugin die extensies ondersteunt"}]},{"@type":"WebSite","@id":"https:\/\/kinsta.com\/nl\/#website","url":"https:\/\/kinsta.com\/nl\/","name":"Kinsta\u00ae","description":"Snelle, veilige, premium hostingoplossingen","publisher":{"@id":"https:\/\/kinsta.com\/nl\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/kinsta.com\/nl\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"nl-NL"},{"@type":"Organization","@id":"https:\/\/kinsta.com\/nl\/#organization","name":"Kinsta","url":"https:\/\/kinsta.com\/nl\/","logo":{"@type":"ImageObject","inLanguage":"nl-NL","@id":"https:\/\/kinsta.com\/nl\/#\/schema\/logo\/image\/","url":"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2023\/12\/kinsta-logo.jpeg","contentUrl":"https:\/\/kinsta.com\/nl\/wp-content\/uploads\/sites\/7\/2023\/12\/kinsta-logo.jpeg","width":500,"height":500,"caption":"Kinsta"},"image":{"@id":"https:\/\/kinsta.com\/nl\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/Kinsta-Nederland-476213452787823\/","https:\/\/x.com\/Kinsta_NL","https:\/\/www.instagram.com\/kinstahosting\/","https:\/\/www.linkedin.com\/company\/kinsta\/","https:\/\/www.pinterest.com\/kinstahosting\/","https:\/\/www.youtube.com\/c\/Kinsta"]},{"@type":"Person","@id":"https:\/\/kinsta.com\/nl\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238","name":"Leonardo Losoviz","image":{"@type":"ImageObject","inLanguage":"nl-NL","@id":"https:\/\/kinsta.com\/nl\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/b28085726ee66e49f08be16ad668efd5?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b28085726ee66e49f08be16ad668efd5?s=96&d=mm&r=g","caption":"Leonardo Losoviz"},"description":"Leo writes about innovative web development trends, mostly concerning PHP, WordPress and GraphQL. You can find him at leoloso.com and twitter.com\/losoviz.","sameAs":["https:\/\/leoloso.com","https:\/\/x.com\/losoviz","https:\/\/www.youtube.com\/@GatoGraphQL"],"url":"https:\/\/kinsta.com\/nl\/blog\/author\/leonardolosoviz\/"}]}},"acf":[],"_links":{"self":[{"href":"https:\/\/kinsta.com\/nl\/wp-json\/wp\/v2\/posts\/58652","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kinsta.com\/nl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kinsta.com\/nl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kinsta.com\/nl\/wp-json\/wp\/v2\/users\/196"}],"replies":[{"embeddable":true,"href":"https:\/\/kinsta.com\/nl\/wp-json\/wp\/v2\/comments?post=58652"}],"version-history":[{"count":9,"href":"https:\/\/kinsta.com\/nl\/wp-json\/wp\/v2\/posts\/58652\/revisions"}],"predecessor-version":[{"id":58807,"href":"https:\/\/kinsta.com\/nl\/wp-json\/wp\/v2\/posts\/58652\/revisions\/58807"}],"alternate":[{"embeddable":true,"hreflang":"en","title":"English","href":"https:\/\/kinsta.com\/nl\/wp-json\/kinsta\/v1\/posts\/58652\/translations\/en"},{"embeddable":true,"hreflang":"it","title":"Italian","href":"https:\/\/kinsta.com\/nl\/wp-json\/kinsta\/v1\/posts\/58652\/translations\/it"},{"embeddable":true,"hreflang":"pt","title":"Portuguese","href":"https:\/\/kinsta.com\/nl\/wp-json\/kinsta\/v1\/posts\/58652\/translations\/pt"},{"embeddable":true,"hreflang":"fr","title":"French","href":"https:\/\/kinsta.com\/nl\/wp-json\/kinsta\/v1\/posts\/58652\/translations\/fr"},{"embeddable":true,"hreflang":"de","title":"German","href":"https:\/\/kinsta.com\/nl\/wp-json\/kinsta\/v1\/posts\/58652\/translations\/de"},{"embeddable":true,"hreflang":"ja","title":"Japanese","href":"https:\/\/kinsta.com\/nl\/wp-json\/kinsta\/v1\/posts\/58652\/translations\/jp"},{"embeddable":true,"hreflang":"es","title":"Spanish","href":"https:\/\/kinsta.com\/nl\/wp-json\/kinsta\/v1\/posts\/58652\/translations\/es"},{"embeddable":true,"hreflang":"nl","title":"Dutch","href":"https:\/\/kinsta.com\/nl\/wp-json\/kinsta\/v1\/posts\/58652\/translations\/nl"},{"href":"https:\/\/kinsta.com\/nl\/wp-json\/kinsta\/v1\/posts\/58652\/tree"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/kinsta.com\/nl\/wp-json\/wp\/v2\/media\/58653"}],"wp:attachment":[{"href":"https:\/\/kinsta.com\/nl\/wp-json\/wp\/v2\/media?parent=58652"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kinsta.com\/nl\/wp-json\/wp\/v2\/tags?post=58652"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/kinsta.com\/nl\/wp-json\/wp\/v2\/topic?post=58652"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}