{"id":77307,"date":"2024-06-19T10:54:11","date_gmt":"2024-06-19T09:54:11","guid":{"rendered":"https:\/\/kinsta.com\/fr\/?p=77307&#038;preview=true&#038;preview_id=77307"},"modified":"2024-06-28T08:37:02","modified_gmt":"2024-06-28T07:37:02","slug":"plugin-wordpress-extensible","status":"publish","type":"post","link":"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/","title":{"rendered":"Comment rendre un plugin WordPress extensible avec des classes PHP"},"content":{"rendered":"<p>Les extensions WordPress peuvent \u00eatre \u00e9tendues avec des fonctionnalit\u00e9s suppl\u00e9mentaires, comme le d\u00e9montrent des extensions populaires telles que <a href=\"https:\/\/kinsta.com\/fr\/blog\/tutoriel-woocommerce\/\">WooCommerce<\/a> et Gravity Forms. Dans l&rsquo;article <a href=\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensions-pro-versions-gratuites\/\">\u00ab Architecturer une extension WordPress pour prendre en charge les extensions \u00bb<\/a>, nous apprenons qu&rsquo;il existe deux fa\u00e7ons principales de rendre une extension WordPress extensible :<\/p>\n<ol>\n<li>En mettant en place des hooks (actions et filtres) pour que les plugins d&rsquo;extension puissent injecter leurs propres fonctionnalit\u00e9s.<\/li>\n<li>En fournissant des classes PHP dont les plugins d&rsquo;extension peuvent h\u00e9riter.<\/li>\n<\/ol>\n<p>La premi\u00e8re m\u00e9thode repose davantage sur la documentation, d\u00e9taillant les crochets disponibles et leur utilisation. La deuxi\u00e8me m\u00e9thode, en revanche, propose un code pr\u00eat \u00e0 l&#8217;emploi pour les extensions, ce qui r\u00e9duit la n\u00e9cessit\u00e9 d&rsquo;une documentation approfondie. C&rsquo;est un avantage car la cr\u00e9ation d&rsquo;une documentation \u00e0 c\u00f4t\u00e9 du code peut compliquer la gestion et la publication de l&rsquo;extension.<\/p>\n<p>Le fait de fournir directement des classes <a href=\"https:\/\/kinsta.com\/php\/\">PHP<\/a> remplace efficacement la documentation par du code. Au lieu d&rsquo;enseigner comment mettre en \u0153uvre une fonctionnalit\u00e9, l&rsquo;extension fournit le code PHP n\u00e9cessaire, ce qui simplifie la t\u00e2che des d\u00e9veloppeurs tiers.<\/p>\n<p>Explorons quelques techniques pour y parvenir, dans le but ultime de favoriser un \u00e9cosyst\u00e8me d&rsquo;int\u00e9grations autour de notre extnsion WordPress.<\/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>D\u00e9finir les classes PHP de base dans le plugin WordPress<\/h2>\n<p>L&rsquo;<a href=\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress\/\">extension WordPress<\/a> comprendra des classes PHP destin\u00e9es \u00e0 \u00eatre utilis\u00e9es par les plugins d&rsquo;extension. Ces classes PHP peuvent ne pas \u00eatre utilis\u00e9es par l&rsquo;extension principale elle-m\u00eame, mais sont fournies sp\u00e9cifiquement pour que d&rsquo;autres puissent les utiliser.<\/p>\n<p>Voyons comment cela est mis en \u0153uvre dans l&rsquo;extension open source <a href=\"https:\/\/wordpress.org\/plugins\/gatographql\/\" target=\"_blank\" rel=\"noopener noreferrer\">Gato GraphQL<\/a>.<\/p>\n<h3>Classe AbstractPlugin :<\/h3>\n<p><a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/f918258371ec4c49c68b64fa184212cf0a956c16\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/src\/PluginSkeleton\/AbstractPlugin.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>AbstractPlugin<\/code><\/a> repr\u00e9sente une extension, \u00e0 la fois pour l&rsquo;extension principale Gato GraphQL et pour ses extensions :<\/p>\n<pre><code class=\"language-php\">abstract class AbstractPlugin implements PluginInterface\n{\n  protected string $pluginBaseName;\n  protected string $pluginSlug;\n  protected string $pluginName;\n\n  public function __construct(\n    protected string $pluginFile,\n    protected string $pluginVersion,\n    ?string $pluginName,\n  ) {\n    $this-&gt;pluginBaseName = plugin_basename($pluginFile);\n    $this-&gt;pluginSlug = dirname($this-&gt;pluginBaseName);\n    $this-&gt;pluginName = $pluginName ?? $this-&gt;pluginBaseName;\n  }\n\n  public function getPluginName(): string\n  {\n    return $this-&gt;pluginName;\n  }\n\n  public function getPluginBaseName(): string\n  {\n    return $this-&gt;pluginBaseName;\n  }\n\n  public function getPluginSlug(): string\n  {\n    return $this-&gt;pluginSlug;\n  }\n\n  public function getPluginFile(): string\n  {\n    return $this-&gt;pluginFile;\n  }\n\n  public function getPluginVersion(): string\n  {\n    return $this-&gt;pluginVersion;\n  }\n\n  public function getPluginDir(): string\n  {\n    return dirname($this-&gt;pluginFile);\n  }\n\n  public function getPluginURL(): string\n  {\n    return plugin_dir_url($this-&gt;pluginFile);\n  }\n\n  \/\/ ...\n}\n<\/code><\/pre>\n<h3>Classe AbstractMainPlugin :<\/h3>\n<p><a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/6545e639ceb85180bf869f50dcaae56de37421ea\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/src\/PluginSkeleton\/AbstractMainPlugin.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>AbstractMainPlugin<\/code><\/a> \u00e9tend <code>AbstractPlugin<\/code> pour repr\u00e9senter l&rsquo;extension principale :<\/p>\n<pre><code class=\"language-php\">abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface\n{\n  public function __construct(\n    string $pluginFile,\n    string $pluginVersion,\n    ?string $pluginName,\n    protected MainPluginInitializationConfigurationInterface $pluginInitializationConfiguration,\n  ) {\n    parent::__construct(\n      $pluginFile,\n      $pluginVersion,\n      $pluginName,\n    );\n  }\n\n  \/\/ ...\n}\n<\/code><\/pre>\n<h3>Classe AbstractExtension :<\/h3>\n<p>De m\u00eame, <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/2447b3478ecfd012d509ad3075149cc0191fb3a1\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/src\/PluginSkeleton\/AbstractExtension.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>AbstractExtension<\/code><\/a> \u00e9tend <code>AbstractPlugin<\/code> pour repr\u00e9senter un plugin d&rsquo;extension :<\/p>\n<pre><code class=\"language-php\">abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface\n{\n  public function __construct(\n    string $pluginFile,\n    string $pluginVersion,\n    ?string $pluginName,\n    protected ?ExtensionInitializationConfigurationInterface $extensionInitializationConfiguration,\n  ) {\n    parent::__construct(\n      $pluginFile,\n      $pluginVersion,\n      $pluginName,\n    );\n  }\n\n  \/\/ ...\n}\n<\/code><\/pre>\n<p>Remarquez que <code>AbstractExtension<\/code> est inclus dans l&rsquo;extension principale, fournissant la fonctionnalit\u00e9 d&rsquo;enregistrement et d&rsquo;initialisation d&rsquo;une extension. Cependant, il n&rsquo;est utilis\u00e9 que par les extensions, et non par l&rsquo;extension principale elle-m\u00eame.<\/p>\n<p>La classe <code>AbstractPlugin<\/code> contient un code d&rsquo;initialisation partag\u00e9, invoqu\u00e9 \u00e0 diff\u00e9rents moments. Ces m\u00e9thodes sont d\u00e9finies au niveau de l&rsquo;anc\u00eatre mais sont invoqu\u00e9es par les classes qui en h\u00e9ritent en fonction de leur cycle de vie.<\/p>\n<p>L&rsquo;extension principale et les autres extensions sont initialis\u00e9es en ex\u00e9cutant la m\u00e9thode <code>setup<\/code> sur la classe correspondante, invoqu\u00e9e depuis le fichier principal de l&rsquo;extnsion WordPress.<\/p>\n<p>Par exemple, dans Gato GraphQL, cela se fait dans <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/135a11e2637c76410a1d23d090fc4ec9b87c01fb\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/gatographql.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>gatographql.php<\/code><\/a>:<\/p>\n<pre><code class=\"language-php\">$pluginFile = __FILE__;\n$pluginVersion = '2.4.0';\n$pluginName = __('Gato GraphQL', 'gatographql');\nPluginApp::getMainPluginManager()-&gt;register(new Plugin(\n  $pluginFile,\n  $pluginVersion,\n  $pluginName\n))-&gt;setup();\n<\/code><\/pre>\n<h3>M\u00e9thode setup :<\/h3>\n<p>Au niveau de l&rsquo;anc\u00eatre, <code>setup<\/code> contient la logique commune entre l&rsquo;extension et ses extensions, comme la d\u00e9sinscription de ces derni\u00e8res lorsque l&rsquo;extension est d\u00e9sactiv\u00e9e. Cette m\u00e9thode n&rsquo;est pas finale ; elle peut \u00eatre surcharg\u00e9e par les classes qui en h\u00e9ritent pour ajouter leurs fonctionnalit\u00e9s :<\/p>\n<pre><code class=\"language-php\">abstract class AbstractPlugin implements PluginInterface\n{\n  \/\/ ...\n\n  public function setup(): void\n  {\n    register_deactivation_hook(\n      $this-&gt;getPluginFile(),\n      $this-&gt;deactivate(...)\n    );\n  }\n\n  public function deactivate(): void\n  {\n    $this-&gt;removePluginVersion();\n  }\n\n  private function removePluginVersion(): void\n  {\n    $pluginVersions = get_option('gatographql-plugin-versions', []);\n    unset($pluginVersions[$this-&gt;pluginBaseName]);\n    update_option('gatographql-plugin-versions', $pluginVersions);\n  }\n}\n<\/code><\/pre>\n<h3>La m\u00e9thode setup du plugin principal :<\/h3>\n<p>La m\u00e9thode <code>setup<\/code> de l&rsquo;extension principale initialise le cycle de vie de l&rsquo;application. Elle ex\u00e9cute les fonctionnalit\u00e9s de l&rsquo;extnsion principale par le biais de m\u00e9thodes telles que <code>initialize<\/code>, <code>configureComponents<\/code>, <code>configure<\/code>, et <code>boot<\/code>, et d\u00e9clenche les crochets d&rsquo;action correspondants pour les extensions :<\/p>\n<pre><code class=\"language-php\">abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface\n{\n  public function setup(): void\n  {\n    parent::setup();\n\n    add_action('plugins_loaded', function (): void\n    {\n      \/\/ 1. Initialize main plugin\n      $this-&gt;initialize();\n\n      \/\/ 2. Initialize extensions\n      do_action('gatographql:initializeExtension');\n\n      \/\/ 3. Configure main plugin components\n      $this-&gt;configureComponents();\n\n      \/\/ 4. Configure extension components\n      do_action('gatographql:configureExtensionComponents');\n\n      \/\/ 5. Configure main plugin\n      $this-&gt;configure();\n\n      \/\/ 6. Configure extension\n      do_action('gatographql:configureExtension');\n\n      \/\/ 7. Boot main plugin\n      $this-&gt;boot();\n\n      \/\/ 8. Boot extension\n      do_action('gatographql:bootExtension');\n    }\n\n    \/\/ ...\n  }\n  \n  \/\/ ...\n}\n<\/code><\/pre>\n<h3>M\u00e9thode setup de l&rsquo;extension :<\/h3>\n<p>La classe <code>AbstractExtension<\/code> ex\u00e9cute sa logique sur les hooks correspondants :<\/p>\n<pre><code class=\"language-php\">abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface\n{\n  \/\/ ...\n\n  final public function setup(): void\n  {\n    parent::setup();\n\n    add_action('plugins_loaded', function (): void\n    {\n      \/\/ 2. Initialize extensions\n      add_action(\n        'gatographql:initializeExtension',\n        $this-&gt;initialize(...)\n      );\n\n      \/\/ 4. Configure extension components\n      add_action(\n        'gatographql:configureExtensionComponents',\n        $this-&gt;configureComponents(...)\n      );\n\n      \/\/ 6. Configure extension\n      add_action(\n        'gatographql:configureExtension',\n        $this-&gt;configure(...)\n      );\n\n      \/\/ 8. Boot extension\n      add_action(\n        'gatographql:bootExtension',\n        $this-&gt;boot(...)\n      );\n    }, 20);\n  }\n}\n<\/code><\/pre>\n<p>Les m\u00e9thodes <code>initialize<\/code>, <code>configureComponents<\/code>, <code>configure<\/code>, et <code>boot<\/code> sont communes \u00e0 l&rsquo;extension principale et aux autres extensions et peuvent partager leur logique. Cette logique partag\u00e9e est stock\u00e9e dans la classe <code>AbstractPlugin<\/code>.<\/p>\n<p>Par exemple, la m\u00e9thode <code>configure<\/code> configure l&rsquo;extension ou les autres extensions, en appelant <code>callPluginInitializationConfiguration<\/code>, qui a des impl\u00e9mentations diff\u00e9rentes pour l&rsquo;extension principale et les autres extensions et qui est d\u00e9fini comme abstrait et <code>getModuleClassConfiguration<\/code>, qui fournit un comportement par d\u00e9faut mais qui peut \u00eatre surcharg\u00e9 si n\u00e9cessaire :<\/p>\n<pre><code class=\"language-php\">abstract class AbstractPlugin implements PluginInterface\n{\n  \/\/ ...\n\n  public function configure(): void\n  {\n    $this-&gt;callPluginInitializationConfiguration();\n\n    $appLoader = App::getAppLoader();\n    $appLoader-&gt;addModuleClassConfiguration($this-&gt;getModuleClassConfiguration());\n  }\n\n  abstract protected function callPluginInitializationConfiguration(): void;\n\n  \/**\n   * @return array&lt;class-string&lt;ModuleInterface&gt;,mixed&gt; [key]: Module class, [value]: Configuration\n   *\/\n  public function getModuleClassConfiguration(): array\n  {\n    return [];\n  }\n}\n<\/code><\/pre>\n<p>L&rsquo;extension principale fournit son impl\u00e9mentation pour <code>callPluginInitializationConfiguration<\/code>:<\/p>\n<pre><code class=\"language-php\">abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface\n{\n  \/\/ ...\n\n  protected function callPluginInitializationConfiguration(): void\n  {\n    $this-&gt;pluginInitializationConfiguration-&gt;initialize();\n  }\n}\n<\/code><\/pre>\n<p>De m\u00eame, la classe d&rsquo;extension fournit son impl\u00e9mentation :<\/p>\n<pre><code class=\"language-php\">abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface\n{\n  \/\/ ...\n\n  protected function callPluginInitializationConfiguration(): void\n  {\n    $this-&gt;extensionInitializationConfiguration?-&gt;initialize();\n  }\n}\n<\/code><\/pre>\n<p>Les m\u00e9thodes <code>initialize<\/code>, <code>configureComponents<\/code> et <code>boot<\/code> sont d\u00e9finies au niveau de l&rsquo;anc\u00eatre et peuvent \u00eatre surcharg\u00e9es par les classes qui en h\u00e9ritent :<\/p>\n<pre><code class=\"language-php\">abstract class AbstractPlugin implements PluginInterface\n{\n  \/\/ ...\n\n  public function initialize(): void\n  {\n    $moduleClasses = $this-&gt;getModuleClassesToInitialize();\n    App::getAppLoader()-&gt;addModuleClassesToInitialize($moduleClasses);\n  }\n\n  \/**\n   * @return array&lt;class-string&lt;ModuleInterface&gt;&gt; List of `Module` class to initialize\n   *\/\n  abstract protected function getModuleClassesToInitialize(): array;\n\n  public function configureComponents(): void\n  {\n    $classNamespace = ClassHelpers::getClassPSR4Namespace(get_called_class());\n    $moduleClass = $classNamespace . '\\\\Module';\n    App::getModule($moduleClass)-&gt;setPluginFolder(dirname($this-&gt;pluginFile));\n  }\n\n  public function boot(): void\n  {\n    \/\/ By default, do nothing\n  }\n}\n<\/code><\/pre>\n<p>Toutes les m\u00e9thodes peuvent \u00eatre remplac\u00e9es par <code>AbstractMainPlugin<\/code> ou <code>AbstractExtension<\/code> pour les \u00e9tendre avec leur fonctionnalit\u00e9 personnalis\u00e9e.<\/p>\n<p>Pour l&rsquo;extension principale, la m\u00e9thode <code>setup<\/code> supprime \u00e9galement toute mise en cache de l&rsquo;instance WordPress lorsque l&rsquo;extension ou l&rsquo;une de ses extensions est activ\u00e9e ou d\u00e9sactiv\u00e9e :<\/p>\n<pre><code class=\"language-php\">abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface\n{\n  public function setup(): void\n  {\n    parent::setup();\n\n    \/\/ ...\n\n    \/\/ Main-plugin specific methods\n    add_action(\n      'activate_plugin',\n      function (string $pluginFile): void {\n        $this-&gt;maybeRegenerateContainerWhenPluginActivatedOrDeactivated($pluginFile);\n      }\n    );\n    add_action(\n      'deactivate_plugin',\n      function (string $pluginFile): void {\n        $this-&gt;maybeRegenerateContainerWhenPluginActivatedOrDeactivated($pluginFile);\n      }\n    );\n  }\n\n  public function maybeRegenerateContainerWhenPluginActivatedOrDeactivated(string $pluginFile): void\n  {\n    \/\/ Removed code for simplicity\n  }\n\n  \/\/ ...\n}\n<\/code><\/pre>\n<p>De m\u00eame, la m\u00e9thode <code>deactivate<\/code> supprime la mise en cache et <code>boot<\/code> ex\u00e9cute des hooks d&rsquo;action suppl\u00e9mentaires pour l&rsquo;extension principale uniquement :<\/p>\n<pre><code class=\"language-php\">abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface\n{\n  public function deactivate(): void\n  {\n    parent::deactivate();\n\n    $this-&gt;removeTimestamps();\n  }\n\n  protected function removeTimestamps(): void\n  {\n    $userSettingsManager = UserSettingsManagerFacade::getInstance();\n    $userSettingsManager-&gt;removeTimestamps();\n  }\n\n  public function boot(): void\n  {\n    parent::boot();\n\n    add_filter(\n      'admin_body_class',\n      function (string $classes): string {\n        $extensions = PluginApp::getExtensionManager()-&gt;getExtensions();\n        $commercialExtensionActivatedLicenseObjectProperties = SettingsHelpers::getCommercialExtensionActivatedLicenseObjectProperties();\n        foreach ($extensions as $extension) {\n          $extensionCommercialExtensionActivatedLicenseObjectProperties = $commercialExtensionActivatedLicenseObjectProperties[$extension-&gt;getPluginSlug()] ?? null;\n          if ($extensionCommercialExtensionActivatedLicenseObjectProperties === null) {\n            continue;\n          }\n          return $classes . ' is-gatographql-customer';\n        }\n        return $classes;\n      }\n    );\n  }\n}\n<\/code><\/pre>\n<p>D&rsquo;apr\u00e8s tout le code pr\u00e9sent\u00e9 ci-dessus, il est clair que lors de la conception et du codage d&rsquo;une extension WordPress, nous devons prendre en compte les besoins de ses extensions et r\u00e9utiliser le code entre elles autant que possible. La mise en \u0153uvre de mod\u00e8les de <a href=\"https:\/\/kinsta.com\/fr\/blog\/php-oop\/\">programmation orient\u00e9e objet<\/a> (tels que les principes <a href=\"https:\/\/dev.to\/evrtrabajo\/solid-in-php-d8e\" target=\"_blank\" rel=\"noopener noreferrer\">SOLID<\/a>) permet d&rsquo;atteindre cet objectif et de rendre la base de code maintenable \u00e0 long terme.<\/p>\n<h2>D\u00e9clarer et valider la d\u00e9pendance de la version<\/h2>\n<p>\u00c9tant donn\u00e9 que l&rsquo;extension h\u00e9rite d&rsquo;une classe PHP fournie par l&rsquo;extension, il est crucial de valider que la version n\u00e9cessaire de l&rsquo;extension est pr\u00e9sente. Si vous ne le faites pas, vous risquez de provoquer des conflits qui entra\u00eeneront l&rsquo;arr\u00eat du site.<\/p>\n<p>Par exemple, si la classe <code>AbstractExtension<\/code> est mise \u00e0 jour avec des changements de rupture et publie une version majeure <code>4.0.0<\/code> par rapport \u00e0 la pr\u00e9c\u00e9dente <code>3.4.0<\/code>, le chargement de l&rsquo;extension sans v\u00e9rification de la version pourrait entra\u00eener une erreur PHP, emp\u00eachant le chargement de WordPress.<\/p>\n<p>Pour \u00e9viter cela, l&rsquo;extension doit valider que l&rsquo;extension install\u00e9e est la version <code>3.x.x<\/code>. Lorsque la version <code>4.0.0<\/code> est install\u00e9e, l&rsquo;extension sera d\u00e9sactiv\u00e9e, ce qui \u00e9vitera les erreurs.<\/p>\n<p>L&rsquo;extension peut effectuer cette validation en utilisant la logique suivante, ex\u00e9cut\u00e9e sur le hook <code>plugins_loaded<\/code> (puisque l&rsquo;extension principale sera charg\u00e9e \u00e0 ce moment-l\u00e0) dans le <a href=\"https:\/\/github.com\/GatoGraphQL\/ExtensionStarter\/blob\/4ce883e3ec5325f15797aaff3042370f25e7e671\/templates\/basic\/layers\/GatoGraphQLForWP\/plugins\/extension-template\/gatographql-extension-template.php\" target=\"_blank\" rel=\"noopener noreferrer\">fichier de plugin principal de l&rsquo;extension<\/a>. Cette logique acc\u00e8de \u00e0 la classe <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/4d33af1ad5c40318b0c2e612d1114c5ba342b06e\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/src\/PluginManagement\/ExtensionManager.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>ExtensionManager<\/code><\/a> qui est incluse dans le plugin principal pour g\u00e9rer les extensions :<\/p>\n<pre><code class=\"language-php\">\/**\n * Create and set-up the extension\n *\/\nadd_action(\n  'plugins_loaded',\n  function (): void {\n    \/**\n     * Extension's name and version.\n     *\n     * Use a stability suffix as supported by Composer.\n     *\/\n    $extensionVersion = '1.1.0';\n    $extensionName = __('Gato GraphQL - Extension Template');\n\n    \/**\n     * The minimum version required from the Gato GraphQL plugin\n     * to activate the extension.\n     *\/\n    $gatoGraphQLPluginVersionConstraint = '^1.0';\n    \n    \/**\n     * Validate Gato GraphQL is active\n     *\/\n    if (!class_exists(\\GatoGraphQL\\GatoGraphQL\\Plugin::class)) {\n      add_action('admin_notices', function () use ($extensionName) {\n        printf(\n          '&lt;div class=\"notice notice-error\"&gt;&lt;p&gt;%s&lt;\/p&gt;&lt;\/div&gt;',\n          sprintf(\n            __('Plugin &lt;strong&gt;%s&lt;\/strong&gt; is not installed or activated. Without it, plugin &lt;strong&gt;%s&lt;\/strong&gt; will not be loaded.'),\n            __('Gato GraphQL'),\n            $extensionName\n          )\n        );\n      });\n      return;\n    }\n\n    $extensionManager = \\GatoGraphQL\\GatoGraphQL\\PluginApp::getExtensionManager();\n    if (!$extensionManager-&gt;assertIsValid(\n      GatoGraphQLExtension::class,\n      $extensionVersion,\n      $extensionName,\n      $gatoGraphQLPluginVersionConstraint\n    )) {\n      return;\n    }\n    \n    \/\/ Load Composer\u2019s autoloader\n    require_once(__DIR__ . '\/vendor\/autoload.php');\n\n    \/\/ Create and set-up the extension instance\n    $extensionManager-&gt;register(new GatoGraphQLExtension(\n      __FILE__,\n      $extensionVersion,\n      $extensionName,\n    ))-&gt;setup();\n  }\n);\n<\/code><\/pre>\n<p>Remarquez que l&rsquo;extension d\u00e9clare une d\u00e9pendance sur la contrainte de version <code>^1.0<\/code> de l&rsquo;extension principale (en utilisant les <a href=\"https:\/\/getcomposer.org\/doc\/articles\/versions.md#writing-version-constraints\" target=\"_blank\" rel=\"noopener noreferrer\">contraintes de version de Composer<\/a>). Ainsi, lorsque la version <code>2.0.0<\/code> de Gato GraphQL est install\u00e9e, l&rsquo;extension ne sera pas activ\u00e9e.<\/p>\n<p>La contrainte de version est valid\u00e9e par la m\u00e9thode <code>ExtensionManager::assertIsValid<\/code>, qui appelle <code>Semver::satisfies<\/code> (fournie par le <a href=\"https:\/\/packagist.org\/packages\/composer\/semver\" target=\"_blank\" rel=\"noopener noreferrer\">paquetage <code>composer\/semver<\/code><\/a>) :<\/p>\n<pre><code class=\"language-php\">use Composer\\Semver\\Semver;\n\nclass ExtensionManager extends AbstractPluginManager\n{\n  \/**\n   * Validate that the required version of the Gato GraphQL for WP plugin is installed.\n   *\n   * If the assertion fails, it prints an error on the WP admin and returns false\n   *\n   * @param string|null $mainPluginVersionConstraint the semver version constraint required for the plugin (eg: \"^1.0\" means &gt;=1.0.0 and &lt;2.0.0)\n   *\/\n  public function assertIsValid(\n    string $extensionClass,\n    string $extensionVersion,\n    ?string $extensionName = null,\n    ?string $mainPluginVersionConstraint = null,\n  ): bool {\n    $mainPlugin = \\GatoGraphQL\\GatoGraphQL\\PluginApp::getMainPluginManager()-&gt;getPlugin();\n    $mainPluginVersion = $mainPlugin-&gt;getPluginVersion();\n    if (\n      $mainPluginVersionConstraint !== null && !Semver::satisfies(\n        $mainPluginVersion,\n        $mainPluginVersionConstraint\n      )\n    ) {\n      $this-&gt;printAdminNoticeErrorMessage(\n        sprintf(\n          __('Extension or bundle &lt;strong&gt;%s&lt;\/strong&gt; requires plugin &lt;strong&gt;%s&lt;\/strong&gt; to satisfy version constraint &lt;code&gt;%s&lt;\/code&gt;, but the current version &lt;code&gt;%s&lt;\/code&gt; does not. The extension or bundle has not been loaded.', 'gatographql'),\n          $extensionName ?? $extensionClass,\n          $mainPlugin-&gt;getPluginName(),\n          $mainPluginVersionConstraint,\n          $mainPlugin-&gt;getPluginVersion(),\n        )\n      );\n      return false;\n    }\n\n    return true;\n  }\n\n  protected function printAdminNoticeErrorMessage(string $errorMessage): void\n  {\n    \\add_action('admin_notices', function () use ($errorMessage): void {\n      $adminNotice_safe = sprintf(\n        '&lt;div class=\"notice notice-error\"&gt;&lt;p&gt;%s&lt;\/p&gt;&lt;\/div&gt;',\n        $errorMessage\n      );\n      echo $adminNotice_safe;\n    });\n  }\n}\n<\/code><\/pre>\n<h2>Ex\u00e9cution de tests d&rsquo;int\u00e9gration sur un serveur WordPress<\/h2>\n<p>Pour que les d\u00e9veloppeurs tiers puissent cr\u00e9er plus facilement des extensions pour vous extnsions, mettez \u00e0 leur disposition des outils de d\u00e9veloppement et de test, notamment des flux de travail pour leurs processus d&rsquo;<a href=\"https:\/\/kinsta.com\/fr\/blog\/tests-automatises\/\">int\u00e9gration et de livraison continues<\/a> (CI\/CD).<\/p>\n<p>Pendant le d\u00e9veloppement, n&rsquo;importe qui peut facilement faire tourner un serveur web \u00e0 l&rsquo;aide de <a href=\"https:\/\/kinsta.com\/fr\/devkinsta\/\">DevKinsta<\/a>, installer l&rsquo;extension pour lequel il code l&rsquo;extension, et valider imm\u00e9diatement que l&rsquo;extension est compatible avec l&rsquo;extension.<\/p>\n<p>Pour automatiser les tests pendant le processus CI\/CD, nous avons besoin que le serveur web soit accessible via un r\u00e9seau au service CI\/CD. Des services tels que InstaWP peuvent cr\u00e9er un site bac \u00e0 sable avec WordPress install\u00e9 \u00e0 cette fin.<\/p>\n<p>Si la base de code de l&rsquo;extension est h\u00e9berg\u00e9e sur <a href=\"https:\/\/kinsta.com\/fr\/blog\/base-de-connaissances-github\/\">GitHub<\/a>, les d\u00e9veloppeurs peuvent utiliser les <a href=\"https:\/\/kinsta.com\/fr\/blog\/configurer-pipeline-ci-cd\/#what-is-github-actions\">Actions GitHub<\/a> pour ex\u00e9cuter des tests d&rsquo;int\u00e9gration contre le service InstaWP. Le flux de travail suivant installe l&rsquo;<a href=\"https:\/\/github.com\/GatoGraphQL\/ExtensionStarter\/blob\/aa574c785a139d8ffc094b3b3703eb516156f77e\/.github\/workflows\/generate_plugins.yml\" target=\"_blank\" rel=\"noopener noreferrer\">extension<\/a> sur un site sandbox InstaWP (aux c\u00f4t\u00e9s de la derni\u00e8re version stable du plugin principal) et <a href=\"https:\/\/github.com\/GatoGraphQL\/ExtensionStarter\/blob\/a2d5d37ed205264f5ee03ce3ad075a08e89b67a7\/.github\/workflows\/integration_tests.yml\">ex\u00e9cute ensuite les tests d&rsquo;int\u00e9gration<\/a>:<\/p>\n<pre><code class=\"language-yaml\">name: Integration tests (InstaWP)\non:\n  workflow_run:\n    workflows: [Generate plugins]\n    types:\n      - completed\n\njobs:\n  provide_data:\n    if: ${{ github.event.workflow_run.conclusion == 'success' }}\n    name: Retrieve the GitHub Action artifact URLs to install in InstaWP\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n\n      - uses: shivammathur\/setup-php@v2\n        with:\n          php-version: 8.1\n          coverage: none\n        env:\n          COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - uses: \"ramsey\/composer-install@v2\"\n\n      - name: Retrieve artifact URLs from GitHub workflow\n        uses: actions\/github-script@v6\n        id: artifact-url\n        with:\n          script: |\n            const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              run_id: context.payload.workflow_run.id,\n            });\n            const artifactURLs = allArtifacts.data.artifacts.map((artifact) =&gt; {\n              return artifact.url.replace('https:\/\/api.github.com\/repos', 'https:\/\/nightly.link') + '.zip'\n            }).concat([\n              \"https:\/\/downloads.wordpress.org\/plugin\/gatographql.latest-stable.zip\"\n            ]);\n            return artifactURLs.join(',');\n          result-encoding: string\n\n      - name: Artifact URL for InstaWP\n        run: echo \"Artifact URL for InstaWP - ${{ steps.artifact-url.outputs.result }}\"\n        shell: bash\n\n    outputs:\n      artifact_url: ${{ steps.artifact-url.outputs.result }}\n\n  process:\n    needs: provide_data\n    name: Launch InstaWP site from template 'integration-tests' and execute integration tests against it\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n\n      - uses: shivammathur\/setup-php@v2\n        with:\n          php-version: 8.1\n          coverage: none\n        env:\n          COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - uses: \"ramsey\/composer-install@v2\"\n\n      - name: Create InstaWP instance\n        uses: instawp\/wordpress-testing-automation@main\n        id: create-instawp\n        with:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          INSTAWP_TOKEN: ${{ secrets.INSTAWP_TOKEN }}\n          INSTAWP_TEMPLATE_SLUG: \"integration-tests\"\n          REPO_ID: 25\n          INSTAWP_ACTION: create-site-template\n          ARTIFACT_URL: ${{ needs.provide_data.outputs.artifact_url }}\n\n      - name: InstaWP instance URL\n        run: echo \"InstaWP instance URL - ${{ steps.create-instawp.outputs.instawp_url }}\"\n        shell: bash\n\n      - name: Extract InstaWP domain\n        id: extract-instawp-domain        \n        run: |\n          instawp_domain=\"$(echo \"${{ steps.create-instawp.outputs.instawp_url }}\" | sed -e s#https:\/\/##)\"\n          echo \"instawp-domain=$(echo $instawp_domain)\" &gt;&gt; $GITHUB_OUTPUT\n\n      - name: Run tests\n        run: |\n          INTEGRATION_TESTS_WEBSERVER_DOMAIN=${{ steps.extract-instawp-domain.outputs.instawp-domain }} \\\n          INTEGRATION_TESTS_AUTHENTICATED_ADMIN_USER_USERNAME=${{ steps.create-instawp.outputs.iwp_wp_username }} \\\n          INTEGRATION_TESTS_AUTHENTICATED_ADMIN_USER_PASSWORD=${{ steps.create-instawp.outputs.iwp_wp_password }} \\\n          vendor\/bin\/phpunit --filter=Integration\n\n      - name: Destroy InstaWP instance\n        uses: instawp\/wordpress-testing-automation@main\n        id: destroy-instawp\n        if: ${{ always() }}\n        with:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          INSTAWP_TOKEN: ${{ secrets.INSTAWP_TOKEN }}\n          INSTAWP_TEMPLATE_SLUG: \"integration-tests\"\n          REPO_ID: 25\n          INSTAWP_ACTION: destroy-site\n<\/code><\/pre>\n<p>Ce workflow acc\u00e8de au fichier <strong>.zip<\/strong> via <a href=\"https:\/\/nightly.link\" target=\"_blank\" rel=\"noopener noreferrer\">Nightly Link<\/a>, un service qui permet d&rsquo;acc\u00e9der \u00e0 un artefact de GitHub sans se connecter, ce qui simplifie la configuration d&rsquo;InstaWP.<\/p>\n<h2>Publication du plugin d&rsquo;extension<\/h2>\n<p>Nous pouvons fournir des outils pour aider \u00e0 lib\u00e9rer les extensions, en automatisant les proc\u00e9dures autant que possible.<\/p>\n<p><a href=\"https:\/\/github.com\/symplify\/monorepo-builder\" target=\"_blank\" rel=\"noopener noreferrer\">Monorepo Builder<\/a> est une biblioth\u00e8que permettant de g\u00e9rer n&rsquo;importe quel projet PHP, y compris une extension WordPress. Elle fournit la commande <code>monorepo-builder release<\/code> pour publier une version du projet, en incr\u00e9mentant le composant majeur, mineur ou correctif de la version conform\u00e9ment au <a href=\"https:\/\/semver.org\" target=\"_blank\" rel=\"noopener noreferrer\">versionnement s\u00e9mantique<\/a>.<\/p>\n<p>Cette commande ex\u00e9cute une s\u00e9rie d&rsquo;<a href=\"https:\/\/github.com\/symplify\/monorepo-builder?tab=readme-ov-file#7-release-flow\" target=\"_blank\" rel=\"noopener noreferrer\">agents de publication<\/a>, qui sont des classes PHP qui ex\u00e9cutent une certaine logique. Les agents par d\u00e9faut comprennent un agent qui cr\u00e9e une page <code>git tag<\/code> avec la nouvelle version et un autre qui pousse la balise vers le d\u00e9p\u00f4t distant. Des agents personnalis\u00e9s peuvent \u00eatre inject\u00e9s avant, apr\u00e8s ou entre ces \u00e9tapes.<\/p>\n<p>Les agents de validation sont configur\u00e9s par le biais d&rsquo;un fichier de configuration :<\/p>\n<pre><code class=\"language-php\">use Symplify\\MonorepoBuilder\\Config\\MBConfig;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\AddTagToChangelogReleaseWorker;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\PushNextDevReleaseWorker;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\PushTagReleaseWorker;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\SetCurrentMutualDependenciesReleaseWorker;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\SetNextMutualDependenciesReleaseWorker;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\TagVersionReleaseWorker;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\UpdateBranchAliasReleaseWorker;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\UpdateReplaceReleaseWorker;\n\nreturn static function (MBConfig $mbConfig): void {\n  \/\/ release workers - in order to execute\n  $mbConfig-&gt;workers([\n    UpdateReplaceReleaseWorker::class,\n    SetCurrentMutualDependenciesReleaseWorker::class,\n    AddTagToChangelogReleaseWorker::class,\n    TagVersionReleaseWorker::class,\n    PushTagReleaseWorker::class,\n    SetNextMutualDependenciesReleaseWorker::class,\n    UpdateBranchAliasReleaseWorker::class,\n    PushNextDevReleaseWorker::class,\n  ]);\n};\n<\/code><\/pre>\n<p>Nous pouvons fournir des workers de lib\u00e9ration personnalis\u00e9s pour augmenter le processus de lib\u00e9ration adapt\u00e9 aux besoins d&rsquo;une extension WordPress. Par exemple, le fichier de configuration <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/af21729cd7e88439156ca1b9cf0231a06354b18f\/src\/OnDemand\/Symplify\/MonorepoBuilder\/Release\/ReleaseWorker\/ConvertStableTagVersionForProdInPluginReadmeFileReleaseWorker.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>InjectStableTagVersionInPluginReadmeFileReleaseWorker<\/code><\/a> d\u00e9finit la nouvelle version comme l&rsquo;entr\u00e9e \u00ab Stable tag \u00bb dans le <a href=\"https:\/\/developer.wordpress.org\/plugins\/wordpress-org\/how-your-readme-txt-works\/\" target=\"_blank\" rel=\"noopener noreferrer\">fichier <strong>readme.txt<\/strong> de l&rsquo;extension<\/a>:<\/p>\n<pre><code class=\"language-php\">use Nette\\Utils\\Strings;\nuse PharIo\\Version\\Version;\nuse Symplify\\SmartFileSystem\\SmartFileInfo;\nuse Symplify\\SmartFileSystem\\SmartFileSystem;\n\nclass InjectStableTagVersionInPluginReadmeFileReleaseWorker implements ReleaseWorkerInterface\n{\n  public function __construct(\n    \/\/ This class is provided by the Monorepo Builder\n    private SmartFileSystem $smartFileSystem,\n  ) {\n  }\n\n  public function getDescription(Version $version): string\n  {\n    return 'Have the \"Stable tag\" point to the new version in the plugin\\'s readme.txt file';\n  }\n\n  public function work(Version $version): void\n  {\n    $replacements = [\n      '\/Stable tag:\\s+[a-z0-9.-]+\/' =&gt; 'Stable tag: ' . $version-&gt;getVersionString(),\n    ];\n    $this-&gt;replaceContentInFiles(['\/readme.txt'], $replacements);\n  }\n\n  \/**\n   * @param string[] $files\n   * @param array&lt;string,string&gt; $regexPatternReplacements regex pattern to search, and its replacement\n   *\/\n  protected function replaceContentInFiles(array $files, array $regexPatternReplacements): void\n  {\n    foreach ($files as $file) {\n      $fileContent = $this-&gt;smartFileSystem-&gt;readFile($file);\n      foreach ($regexPatternReplacements as $regexPattern =&gt; $replacement) {\n        $fileContent = Strings::replace($fileContent, $regexPattern, $replacement);\n      }\n      $this-&gt;smartFileSystem-&gt;dumpFile($file, $fileContent);\n    }\n  }\n}\n<\/code><\/pre>\n<p>En ajoutant <code>InjectStableTagVersionInPluginReadmeFileReleaseWorker<\/code> \u00e0 la liste de configuration, chaque fois que l&rsquo;on ex\u00e9cute la commande <code>monorepo-builder release<\/code> pour publier une nouvelle version de l&rsquo;extension, \u00ab Stable tag \u00bb dans le fichier <strong>readme.txt<\/strong> de l&rsquo;extension sera automatiquement mise \u00e0 jour.<\/p>\n<h2>Publication du plugin d&rsquo;extension dans le r\u00e9pertoire WP.org<\/h2>\n<p>Nous pouvons \u00e9galement distribuer un flux de travail pour aider \u00e0 publier l&rsquo;extension dans le <a href=\"https:\/\/wordpress.org\/plugins\/\" target=\"_blank\" rel=\"noopener noreferrer\">r\u00e9pertoire des extensions WordPress<\/a>. Lors du marquage du projet sur le d\u00e9p\u00f4t distant, le <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/f8579139f1af05aae171e776c6f53b0c458e339a\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/.github\/workflows\/deploy_wordpress_plugin_to_svn.yml\" target=\"_blank\" rel=\"noopener noreferrer\">flux de travail suivant<\/a> publiera le plugin d&rsquo;extension WordPress dans le d\u00e9p\u00f4t :<\/p>\n<pre><code class=\"language-yml\"># See: https:\/\/github.com\/10up\/action-wordpress-plugin-deploy#deploy-on-pushing-a-new-tag\nname: Deploy to WordPress.org Plugin Directory (SVN)\non:\n  push:\n  tags:\n  - \"*\"\n\njobs:\n  tag:\n  name: New tag\n  runs-on: ubuntu-latest\n  steps:\n  - uses: actions\/checkout@master\n  - name: WordPress Plugin Deploy\n    uses: 10up\/action-wordpress-plugin-deploy@stable\n    env:\n    SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}\n    SVN_USERNAME: ${{ secrets.SVN_USERNAME }}\n    SLUG: ${{ secrets.SLUG }}\n<\/code><\/pre>\n<p>Ce flux de travail utilise l&rsquo;action <a href=\"https:\/\/github.com\/10up\/action-wordpress-plugin-deploy\" target=\"_blank\" rel=\"noopener noreferrer\"><code>10up\/action-wordpress-plugin-deploy<\/code><\/a> qui r\u00e9cup\u00e8re le code d&rsquo;un d\u00e9p\u00f4t Git et le pousse vers le <a href=\"https:\/\/kinsta.com\/fr\/blog\/publier-plugin-annuaire-wordpress\/#plugin-structure\">d\u00e9p\u00f4t SVN de WordPress.org<\/a>, ce qui simplifie l&rsquo;op\u00e9ration.<\/p>\n<h2>R\u00e9sum\u00e9<\/h2>\n<p>Lorsque l&rsquo;on cr\u00e9e une extension extensible pour WordPress, notre objectif est de faire en sorte qu&rsquo;il soit aussi facile que possible pour les d\u00e9veloppeurs tiers de l&rsquo;\u00e9tendre, maximisant ainsi les chances de favoriser un \u00e9cosyst\u00e8me dynamique autour de nos extensions.<\/p>\n<p>Bien qu&rsquo;une documentation compl\u00e8te puisse guider les d\u00e9veloppeurs sur la fa\u00e7on d&rsquo;\u00e9tendre l&rsquo;extension, une approche encore plus efficace consiste \u00e0 fournir le code PHP et les outils n\u00e9cessaires au d\u00e9veloppement, aux tests et \u00e0 la publication de leurs extensions.<\/p>\n<p>En incluant le code suppl\u00e9mentaire n\u00e9cessaire aux extensions directement dans notre extension, nous simplifions le processus pour les d\u00e9veloppeurs.<\/p>\n<p><em>Avez-vous l&rsquo;intention de rendre votre extension WordPress extensible ? Faites-le nous savoir dans la section des commentaires.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Les extensions WordPress peuvent \u00eatre \u00e9tendues avec des fonctionnalit\u00e9s suppl\u00e9mentaires, comme le d\u00e9montrent des extensions populaires telles que WooCommerce et Gravity Forms. Dans l&rsquo;article \u00ab Architecturer &#8230;<\/p>\n","protected":false},"author":196,"featured_media":77308,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_kinsta_gated_content":false,"_kinsta_gated_content_redirect":"","footnotes":""},"tags":[],"topic":[1028,1035],"class_list":["post-77307","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","topic-developpement-wordpress","topic-extensions-wordpress"],"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>Rendre une extension WordPress extensible avec des classes PHP - Kinsta\u00ae<\/title>\n<meta name=\"description\" content=\"Apprenez \u00e0 cr\u00e9er une extension WordPress qui simplifie le processus de d\u00e9veloppement pour les d\u00e9veloppeurs tiers.\" \/>\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\/fr\/blog\/plugin-wordpress-extensible\/\" \/>\n<meta property=\"og:locale\" content=\"fr_FR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Comment rendre un plugin WordPress extensible avec des classes PHP\" \/>\n<meta property=\"og:description\" content=\"Apprenez \u00e0 cr\u00e9er une extension WordPress qui simplifie le processus de d\u00e9veloppement pour les d\u00e9veloppeurs tiers.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/\" \/>\n<meta property=\"og:site_name\" content=\"Kinsta\u00ae\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/kinstafrance\/\" \/>\n<meta property=\"article:published_time\" content=\"2024-06-19T09:54:11+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-06-28T07:37:02+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-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=\"Apprenez \u00e0 cr\u00e9er une extension WordPress qui simplifie le processus de d\u00e9veloppement pour les d\u00e9veloppeurs tiers.\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions-1024x512.jpg\" \/>\n<meta name=\"twitter:creator\" content=\"@losoviz\" \/>\n<meta name=\"twitter:site\" content=\"@kinsta_fr\" \/>\n<meta name=\"twitter:label1\" content=\"\u00c9crit par\" \/>\n\t<meta name=\"twitter:data1\" content=\"Leonardo Losoviz\" \/>\n\t<meta name=\"twitter:label2\" content=\"Dur\u00e9e de lecture estim\u00e9e\" \/>\n\t<meta name=\"twitter:data2\" content=\"17 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/\"},\"author\":{\"name\":\"Leonardo Losoviz\",\"@id\":\"https:\/\/kinsta.com\/fr\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238\"},\"headline\":\"Comment rendre un plugin WordPress extensible avec des classes PHP\",\"datePublished\":\"2024-06-19T09:54:11+00:00\",\"dateModified\":\"2024-06-28T07:37:02+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/\"},\"wordCount\":1822,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/kinsta.com\/fr\/#organization\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg\",\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/\",\"url\":\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/\",\"name\":\"Rendre une extension WordPress extensible avec des classes PHP - Kinsta\u00ae\",\"isPartOf\":{\"@id\":\"https:\/\/kinsta.com\/fr\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg\",\"datePublished\":\"2024-06-19T09:54:11+00:00\",\"dateModified\":\"2024-06-28T07:37:02+00:00\",\"description\":\"Apprenez \u00e0 cr\u00e9er une extension WordPress qui simplifie le processus de d\u00e9veloppement pour les d\u00e9veloppeurs tiers.\",\"breadcrumb\":{\"@id\":\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#breadcrumb\"},\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#primaryimage\",\"url\":\"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg\",\"contentUrl\":\"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg\",\"width\":1460,\"height\":730},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/kinsta.com\/fr\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Extensions WordPress\",\"item\":\"https:\/\/kinsta.com\/fr\/sujets\/extensions-wordpress\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Comment rendre un plugin WordPress extensible avec des classes PHP\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/kinsta.com\/fr\/#website\",\"url\":\"https:\/\/kinsta.com\/fr\/\",\"name\":\"Kinsta\u00ae\",\"description\":\"Solutions d&#039;h\u00e9bergement premium, rapides et s\u00e9curis\u00e9es\",\"publisher\":{\"@id\":\"https:\/\/kinsta.com\/fr\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/kinsta.com\/fr\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"fr-FR\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/kinsta.com\/fr\/#organization\",\"name\":\"Kinsta\",\"url\":\"https:\/\/kinsta.com\/fr\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\/\/kinsta.com\/fr\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2023\/12\/kinsta-logo.jpeg\",\"contentUrl\":\"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2023\/12\/kinsta-logo.jpeg\",\"width\":500,\"height\":500,\"caption\":\"Kinsta\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/fr\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/kinstafrance\/\",\"https:\/\/x.com\/kinsta_fr\",\"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\/fr\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238\",\"name\":\"Leonardo Losoviz\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\/\/kinsta.com\/fr\/#\/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\/fr\/blog\/author\/leonardolosoviz\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Rendre une extension WordPress extensible avec des classes PHP - Kinsta\u00ae","description":"Apprenez \u00e0 cr\u00e9er une extension WordPress qui simplifie le processus de d\u00e9veloppement pour les d\u00e9veloppeurs tiers.","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\/fr\/blog\/plugin-wordpress-extensible\/","og_locale":"fr_FR","og_type":"article","og_title":"Comment rendre un plugin WordPress extensible avec des classes PHP","og_description":"Apprenez \u00e0 cr\u00e9er une extension WordPress qui simplifie le processus de d\u00e9veloppement pour les d\u00e9veloppeurs tiers.","og_url":"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/","og_site_name":"Kinsta\u00ae","article_publisher":"https:\/\/www.facebook.com\/kinstafrance\/","article_published_time":"2024-06-19T09:54:11+00:00","article_modified_time":"2024-06-28T07:37:02+00:00","og_image":[{"width":1460,"height":730,"url":"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg","type":"image\/jpeg"}],"author":"Leonardo Losoviz","twitter_card":"summary_large_image","twitter_description":"Apprenez \u00e0 cr\u00e9er une extension WordPress qui simplifie le processus de d\u00e9veloppement pour les d\u00e9veloppeurs tiers.","twitter_image":"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions-1024x512.jpg","twitter_creator":"@losoviz","twitter_site":"@kinsta_fr","twitter_misc":{"\u00c9crit par":"Leonardo Losoviz","Dur\u00e9e de lecture estim\u00e9e":"17 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#article","isPartOf":{"@id":"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/"},"author":{"name":"Leonardo Losoviz","@id":"https:\/\/kinsta.com\/fr\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238"},"headline":"Comment rendre un plugin WordPress extensible avec des classes PHP","datePublished":"2024-06-19T09:54:11+00:00","dateModified":"2024-06-28T07:37:02+00:00","mainEntityOfPage":{"@id":"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/"},"wordCount":1822,"commentCount":0,"publisher":{"@id":"https:\/\/kinsta.com\/fr\/#organization"},"image":{"@id":"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#primaryimage"},"thumbnailUrl":"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg","inLanguage":"fr-FR","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/","url":"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/","name":"Rendre une extension WordPress extensible avec des classes PHP - Kinsta\u00ae","isPartOf":{"@id":"https:\/\/kinsta.com\/fr\/#website"},"primaryImageOfPage":{"@id":"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#primaryimage"},"image":{"@id":"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#primaryimage"},"thumbnailUrl":"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg","datePublished":"2024-06-19T09:54:11+00:00","dateModified":"2024-06-28T07:37:02+00:00","description":"Apprenez \u00e0 cr\u00e9er une extension WordPress qui simplifie le processus de d\u00e9veloppement pour les d\u00e9veloppeurs tiers.","breadcrumb":{"@id":"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#breadcrumb"},"inLanguage":"fr-FR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/"]}]},{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#primaryimage","url":"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg","contentUrl":"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg","width":1460,"height":730},{"@type":"BreadcrumbList","@id":"https:\/\/kinsta.com\/fr\/blog\/plugin-wordpress-extensible\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/kinsta.com\/fr\/"},{"@type":"ListItem","position":2,"name":"Extensions WordPress","item":"https:\/\/kinsta.com\/fr\/sujets\/extensions-wordpress\/"},{"@type":"ListItem","position":3,"name":"Comment rendre un plugin WordPress extensible avec des classes PHP"}]},{"@type":"WebSite","@id":"https:\/\/kinsta.com\/fr\/#website","url":"https:\/\/kinsta.com\/fr\/","name":"Kinsta\u00ae","description":"Solutions d&#039;h\u00e9bergement premium, rapides et s\u00e9curis\u00e9es","publisher":{"@id":"https:\/\/kinsta.com\/fr\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/kinsta.com\/fr\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"fr-FR"},{"@type":"Organization","@id":"https:\/\/kinsta.com\/fr\/#organization","name":"Kinsta","url":"https:\/\/kinsta.com\/fr\/","logo":{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/kinsta.com\/fr\/#\/schema\/logo\/image\/","url":"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2023\/12\/kinsta-logo.jpeg","contentUrl":"https:\/\/kinsta.com\/fr\/wp-content\/uploads\/sites\/4\/2023\/12\/kinsta-logo.jpeg","width":500,"height":500,"caption":"Kinsta"},"image":{"@id":"https:\/\/kinsta.com\/fr\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/kinstafrance\/","https:\/\/x.com\/kinsta_fr","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\/fr\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238","name":"Leonardo Losoviz","image":{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/kinsta.com\/fr\/#\/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\/fr\/blog\/author\/leonardolosoviz\/"}]}},"acf":[],"_links":{"self":[{"href":"https:\/\/kinsta.com\/fr\/wp-json\/wp\/v2\/posts\/77307","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kinsta.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kinsta.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kinsta.com\/fr\/wp-json\/wp\/v2\/users\/196"}],"replies":[{"embeddable":true,"href":"https:\/\/kinsta.com\/fr\/wp-json\/wp\/v2\/comments?post=77307"}],"version-history":[{"count":5,"href":"https:\/\/kinsta.com\/fr\/wp-json\/wp\/v2\/posts\/77307\/revisions"}],"predecessor-version":[{"id":77402,"href":"https:\/\/kinsta.com\/fr\/wp-json\/wp\/v2\/posts\/77307\/revisions\/77402"}],"alternate":[{"embeddable":true,"hreflang":"en","title":"English","href":"https:\/\/kinsta.com\/fr\/wp-json\/kinsta\/v1\/posts\/77307\/translations\/en"},{"embeddable":true,"hreflang":"it","title":"Italian","href":"https:\/\/kinsta.com\/fr\/wp-json\/kinsta\/v1\/posts\/77307\/translations\/it"},{"embeddable":true,"hreflang":"pt","title":"Portuguese","href":"https:\/\/kinsta.com\/fr\/wp-json\/kinsta\/v1\/posts\/77307\/translations\/pt"},{"embeddable":true,"hreflang":"fr","title":"French","href":"https:\/\/kinsta.com\/fr\/wp-json\/kinsta\/v1\/posts\/77307\/translations\/fr"},{"embeddable":true,"hreflang":"de","title":"German","href":"https:\/\/kinsta.com\/fr\/wp-json\/kinsta\/v1\/posts\/77307\/translations\/de"},{"embeddable":true,"hreflang":"ja","title":"Japanese","href":"https:\/\/kinsta.com\/fr\/wp-json\/kinsta\/v1\/posts\/77307\/translations\/jp"},{"embeddable":true,"hreflang":"nl","title":"Dutch","href":"https:\/\/kinsta.com\/fr\/wp-json\/kinsta\/v1\/posts\/77307\/translations\/nl"},{"embeddable":true,"hreflang":"es","title":"Spanish","href":"https:\/\/kinsta.com\/fr\/wp-json\/kinsta\/v1\/posts\/77307\/translations\/es"},{"href":"https:\/\/kinsta.com\/fr\/wp-json\/kinsta\/v1\/posts\/77307\/tree"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/kinsta.com\/fr\/wp-json\/wp\/v2\/media\/77308"}],"wp:attachment":[{"href":"https:\/\/kinsta.com\/fr\/wp-json\/wp\/v2\/media?parent=77307"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kinsta.com\/fr\/wp-json\/wp\/v2\/tags?post=77307"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/kinsta.com\/fr\/wp-json\/wp\/v2\/topic?post=77307"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}