{"id":75477,"date":"2024-06-19T11:53:42","date_gmt":"2024-06-19T09:53:42","guid":{"rendered":"https:\/\/kinsta.com\/es\/?p=75477&#038;preview=true&#038;preview_id=75477"},"modified":"2024-06-28T09:58:08","modified_gmt":"2024-06-28T07:58:08","slug":"plugin-extensible-wordpress","status":"publish","type":"post","link":"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/","title":{"rendered":"C\u00f3mo hacer extensible un plugin de WordPress con clases PHP"},"content":{"rendered":"<p>Los plugins de WordPress pueden ampliarse con funciones adicionales, como demuestran plugins populares como <a href=\"https:\/\/kinsta.com\/es\/blog\/tutorial-de-woocommerce\/\">WooCommerce<\/a> y Gravity Forms. En el art\u00edculo <a href=\"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/\">\u00abDise\u00f1ar un plugin de WordPress para que admita extensiones<\/a>\u00ab, aprendemos que hay dos formas principales de hacer que un plugin de WordPress sea extensible:<\/p>\n<ol>\n<li>Configurando hooks (acciones y filtros) para que los plugins de extensi\u00f3n inyecten su propia funcionalidad.<\/li>\n<li>Proporcionando clases PHP que los plugins de extensi\u00f3n puedan heredar<\/li>\n<\/ol>\n<p>El primer m\u00e9todo se basa m\u00e1s en la documentaci\u00f3n, detallando los hooks disponibles y su uso. El segundo m\u00e9todo, por el contrario, ofrece c\u00f3digo listo para usar por las extensiones, reduciendo la necesidad de una extensa documentaci\u00f3n. Esto es ventajoso porque crear documentaci\u00f3n junto al c\u00f3digo puede complicar la gesti\u00f3n y liberaci\u00f3n del plugin.<\/p>\n<p>Proporcionar directamente clases <a href=\"https:\/\/kinsta.com\/php\/\">PHP<\/a> sustituye eficazmente la documentaci\u00f3n por c\u00f3digo. En lugar de ense\u00f1ar c\u00f3mo implementar una funci\u00f3n, el plugin proporciona el c\u00f3digo PHP necesario, simplificando la tarea a los desarrolladores externos.<\/p>\n<p>Vamos a explorar algunas t\u00e9cnicas para conseguirlo, con el objetivo final de fomentar un ecosistema de integraciones en torno a nuestro plugin de 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>Definici\u00f3n de clases base en PHP en el plugin de WordPress<\/h2>\n<p>El <a href=\"https:\/\/kinsta.com\/es\/blog\/wordpress-plugin\/\">plugin de WordPress<\/a> incluir\u00e1 clases PHP destinadas a ser utilizadas por plugins de extensi\u00f3n. Estas clases PHP podr\u00edan no ser utilizadas por el propio plugin principal, sino que se proporcionan espec\u00edficamente para que otros las utilicen.<\/p>\n<p>Veamos c\u00f3mo se implementa esto en el plugin de c\u00f3digo abierto <a href=\"https:\/\/wordpress.org\/plugins\/gatographql\/\">Gato GraphQL<\/a>.<\/p>\n<h3>Clase 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> representa un plugin, tanto para el plugin principal Gato GraphQL como para sus extensiones:<\/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>Clase 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> extiende <code>AbstractPlugin<\/code> para representar el plugin principal:<\/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>Clase AbstractExtension:<\/h3>\n<p>Del mismo modo <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> extiende <code>AbstractPlugin<\/code> para representar un plugin de extensi\u00f3n:<\/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>Observa que <code>AbstractExtension<\/code> se incluye dentro del plugin principal, proporcionando funcionalidad para registrar e inicializar una extensi\u00f3n. Sin embargo, s\u00f3lo lo utilizan las extensiones, no el propio plugin principal.<\/p>\n<p>La clase <code>AbstractPlugin<\/code> contiene c\u00f3digo de inicializaci\u00f3n compartido que se invoca en distintos momentos. Estos m\u00e9todos se definen en el nivel de la clase base, pero son invocados por las clases herederas seg\u00fan sus ciclos de vida.<\/p>\n<p>El plugin principal y las extensiones se inicializan ejecutando el m\u00e9todo <code>setup<\/code> en la clase correspondiente, invocado desde dentro del archivo principal del plugin de WordPress.<\/p>\n<p>Por ejemplo, en Gato GraphQL, esto se hace en <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\u00e9todo setup:<\/h3>\n<p>En el nivel de la clase base, <code>setup<\/code> contiene la l\u00f3gica com\u00fan entre el plugin y sus extensiones, como anular su registro cuando se desactiva el plugin. Este m\u00e9todo no es final; puede ser sobrescrito por las clases herederas para a\u00f1adir su funcionalidad:<\/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>M\u00e9todo setup del plugin principal:<\/h3>\n<p>El m\u00e9todo <code>setup<\/code> del plugin principal inicializa el ciclo de vida de la aplicaci\u00f3n. Ejecuta la funcionalidad del plugin principal a trav\u00e9s de m\u00e9todos como <code>initialize<\/code>, <code>configureComponents<\/code>, <code>configure<\/code>, y <code>boot<\/code>, y activa los hooks de acci\u00f3n correspondientes para las extensiones:<\/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\u00e9todo de configuraci\u00f3n de extensiones:<\/h3>\n<p>La clase <code>AbstractExtension<\/code> ejecuta su l\u00f3gica en los hooks correspondientes:<\/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>Los m\u00e9todos <code>initialize<\/code>, <code>configureComponents<\/code>, <code>configure<\/code>, y <code>boot<\/code> son comunes tanto al plugin principal como a las extensiones y pueden compartir l\u00f3gica. Esta l\u00f3gica compartida se almacena en la clase <code>AbstractPlugin<\/code>.<\/p>\n<p>Por ejemplo, el m\u00e9todo <code>configure<\/code> configura el plugin o las extensiones, llamando a <code>callPluginInitializationConfiguration<\/code>, que tiene implementaciones diferentes para el plugin principal y las extensiones y se define como abstracto, y a <code>getModuleClassConfiguration<\/code>, que proporciona un comportamiento por defecto pero que puede sobrescribirse si es necesario:<\/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>El plugin principal proporciona su implementaci\u00f3n para <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>Del mismo modo, la clase de extensi\u00f3n proporciona su implementaci\u00f3n:<\/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>Los m\u00e9todos <code>initialize<\/code>, <code>configureComponents<\/code> y <code>boot<\/code> se definen en el nivel de la clase base y pueden ser sobrescritos por las clases herederas:<\/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>Todos los m\u00e9todos pueden ser sobrescritos por <code>AbstractMainPlugin<\/code> o <code>AbstractExtension<\/code> para ampliarlos con su funcionalidad personalizada.<\/p>\n<p>Para el plugin principal, el m\u00e9todo <code>setup<\/code> tambi\u00e9n elimina cualquier almacenamiento en cach\u00e9 de la instancia de WordPress cuando se activa o desactiva el plugin o cualquiera de sus extensiones:<\/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>Del mismo modo, el m\u00e9todo <code>deactivate<\/code> elimina el cach\u00e9 y <code>boot<\/code> ejecuta hooks de acci\u00f3n adicionales s\u00f3lo para el plugin principal:<\/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>De todo el c\u00f3digo presentado anteriormente, queda claro que cuando dise\u00f1amos y programamos un plugin de WordPress, debemos tener en cuenta las necesidades de sus extensiones y reutilizar el c\u00f3digo en ellas tanto como sea posible. Implementar patrones s\u00f3lidos de <a href=\"https:\/\/kinsta.com\/es\/blog\/php-oop\/\">Programaci\u00f3n Orientada a Objetos<\/a> (como los principios <a href=\"https:\/\/dev.to\/evrtrabajo\/solid-in-php-d8e\">SOLID<\/a>) ayuda a conseguirlo, haciendo que la base de c\u00f3digo sea mantenible a largo plazo.<\/p>\n<h2>Declarar y validar la dependencia de la versi\u00f3n<\/h2>\n<p>Puesto que la extensi\u00f3n hereda de una clase PHP proporcionada por el plugin, es crucial validar que la versi\u00f3n requerida del plugin est\u00e1 presente. No hacerlo podr\u00eda causar conflictos que hagan caer el sitio.<\/p>\n<p>Por ejemplo, si la clase <code>AbstractExtension<\/code> se actualiza con cambios importantes y libera una versi\u00f3n principal <code>4.0.0<\/code> desde la\u00a0 versi\u00f3n anterior <code>3.4.0<\/code>, cargar la extensi\u00f3n sin comprobar la versi\u00f3n podr\u00eda provocar un error PHP, impidiendo la carga de WordPress.<\/p>\n<p>Para evitarlo, la extensi\u00f3n debe validar que el plugin instalado es la versi\u00f3n <code>3.x.x<\/code>. Cuando se instale la versi\u00f3n <code>4.0.0<\/code>, la extensi\u00f3n se desactivar\u00e1, evitando as\u00ed errores.<\/p>\n<p>La extensi\u00f3n puede realizar esta validaci\u00f3n utilizando la siguiente l\u00f3gica, que se ejecuta en el hook <code>plugins_loaded<\/code> (ya que el plugin principal estar\u00e1 cargado para entonces) en el <a href=\"https:\/\/github.com\/GatoGraphQL\/ExtensionStarter\/blob\/4ce883e3ec5325f15797aaff3042370f25e7e671\/templates\/basic\/layers\/GatoGraphQLForWP\/plugins\/extension-template\/gatographql-extension-template.php\">archivo del plugin principal de la extensi\u00f3n<\/a>. Esta l\u00f3gica accede a la clase <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> que se incluye en el plugin principal para gestionar las extensiones:<\/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>Observa c\u00f3mo la extensi\u00f3n declara una dependencia de la restricci\u00f3n de versi\u00f3n <code>^1.0<\/code> del plugin principal (utilizando las <a href=\"https:\/\/getcomposer.org\/doc\/articles\/versions.md#writing-version-constraints\" target=\"_blank\" rel=\"noopener noreferrer\">restricciones de versi\u00f3n de Composer<\/a>). As\u00ed, cuando se instale la versi\u00f3n <code>2.0.0<\/code> de Gato GraphQL, la extensi\u00f3n no se activar\u00e1.<\/p>\n<p>La restricci\u00f3n de versi\u00f3n se valida mediante el m\u00e9todo <code>ExtensionManager::assertIsValid<\/code>, que llama a <code>Semver::satisfies<\/code> (proporcionado por el <a href=\"https:\/\/packagist.org\/packages\/composer\/semver\" target=\"_blank\" rel=\"noopener noreferrer\">paquete <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>Ejecutar pruebas de integraci\u00f3n contra un servidor WordPress<\/h2>\n<p>Para facilitar a los desarrolladores externos la creaci\u00f3n de extensiones para tus plugins, proporci\u00f3nales herramientas para el desarrollo y las pruebas, incluidos flujos de trabajo para sus procesos de <a href=\"https:\/\/kinsta.com\/es\/blog\/pruebas-automatizadas\/\">integraci\u00f3n continua y entrega continua<\/a> (CI\/CD).<\/p>\n<p>Durante el desarrollo, cualquiera puede poner en marcha f\u00e1cilmente un servidor web con <a href=\"https:\/\/kinsta.com\/es\/devkinsta\/\">DevKinsta<\/a>, instalar el plugin para el que est\u00e1 programando la extensi\u00f3n y validar inmediatamente que la extensi\u00f3n es compatible con el plugin.<\/p>\n<p>Para automatizar las pruebas durante el CI\/CD, necesitamos que el servidor web sea accesible a trav\u00e9s de una red al servicio CI\/CD. Servicios como InstaWP pueden crear un sitio sandbox con WordPress instalado para este fin.<\/p>\n<p>Si el c\u00f3digo base de la extensi\u00f3n est\u00e1 alojado en <a href=\"https:\/\/kinsta.com\/es\/blog\/que-es-github\/\">GitHub<\/a>, los desarrolladores pueden utilizar las <a href=\"https:\/\/kinsta.com\/es\/blog\/como-configurar-pipeline-ci-cd\/#what-is-github-actions\">Acciones de GitHub<\/a> para ejecutar pruebas de integraci\u00f3n contra el servicio InstaWP. El siguiente flujo de trabajo instala la <a href=\"https:\/\/github.com\/GatoGraphQL\/ExtensionStarter\/blob\/aa574c785a139d8ffc094b3b3703eb516156f77e\/.github\/workflows\/generate_plugins.yml\" target=\"_blank\" rel=\"noopener noreferrer\">extensi\u00f3n<\/a> en un sitio sandbox de InstaWP (junto con la \u00faltima versi\u00f3n estable del plugin principal) y, a continuaci\u00f3n, <a href=\"https:\/\/github.com\/GatoGraphQL\/ExtensionStarter\/blob\/a2d5d37ed205264f5ee03ce3ad075a08e89b67a7\/.github\/workflows\/integration_tests.yml\">ejecuta las pruebas de integraci\u00f3n<\/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>Este flujo de trabajo accede al archivo <strong>.zip<\/strong> a trav\u00e9s de <a href=\"https:\/\/nightly.link\" target=\"_blank\" rel=\"noopener noreferrer\">Nightly Link<\/a>, un servicio que permite acceder a un artefacto desde GitHub sin iniciar sesi\u00f3n, simplificando la configuraci\u00f3n de InstaWP.<\/p>\n<h2>Liberar el plugin de extensi\u00f3n<\/h2>\n<p>Podemos proporcionar herramientas para ayudar a liberar las extensiones, automatizando los procedimientos tanto como sea posible.<\/p>\n<p><a href=\"https:\/\/github.com\/symplify\/monorepo-builder\" target=\"_blank\" rel=\"noopener noreferrer\">Monorepo Builder<\/a> es una biblioteca para gestionar cualquier proyecto PHP, incluido un plugin de WordPress. Proporciona el comando <code>monorepo-builder release<\/code> para liberar una versi\u00f3n del proyecto, incrementando el componente mayor, menor o parche de la versi\u00f3n seg\u00fan el <a href=\"https:\/\/semver.org\" target=\"_blank\" rel=\"noopener noreferrer\">versionado sem\u00e1ntico<\/a>.<\/p>\n<p>Este comando ejecuta una serie de <a href=\"https:\/\/github.com\/symplify\/monorepo-builder?tab=readme-ov-file#7-release-flow\" target=\"_blank\" rel=\"noopener noreferrer\">release workers<\/a>, que son clases PHP que ejecutan cierta l\u00f3gica. Los workers por defecto incluyen uno que crea un <code>git tag<\/code> con la nueva versi\u00f3n y otro que env\u00eda la etiqueta al repositorio remoto. Se pueden inyectar workers personalizados antes, despu\u00e9s o entre estos pasos.<\/p>\n<p>Los workers de publicaci\u00f3n se configuran mediante un archivo de configuraci\u00f3n:<\/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>Podemos proporcionar release workers personalizados para aumentar el proceso de liberaci\u00f3n adaptado a las necesidades de un plugin de WordPress. Por ejemplo, el <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> establece la nueva versi\u00f3n como la entrada \u00abStable tag\u00bb en el <a href=\"https:\/\/developer.wordpress.org\/plugins\/wordpress-org\/how-your-readme-txt-works\/\">archivo <strong>readme.txt<\/strong> de la extensi\u00f3n<\/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>A\u00f1adiendo <code>InjectStableTagVersionInPluginReadmeFileReleaseWorker<\/code> a la lista de configuraci\u00f3n, cada vez que ejecutes el comando <code>monorepo-builder release<\/code> para liberar una nueva versi\u00f3n del plugin, se actualizar\u00e1 autom\u00e1ticamente \u00abStable tag\u00bb del archivo <strong>readme.txt<\/strong> de la extensi\u00f3n.<\/p>\n<h2>Publicar el plugin de extensi\u00f3n en el directorio WP.org<\/h2>\n<p>Tambi\u00e9n podemos distribuir un flujo de trabajo para ayudar a publicar la extensi\u00f3n en el <a href=\"https:\/\/wordpress.org\/plugins\/\">Directorio de Plugins de WordPress<\/a>. Al etiquetar el proyecto en el repositorio remoto, el <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/f8579139f1af05aae171e776c6f53b0c458e339a\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/.github\/workflows\/deploy_wordpress_plugin_to_svn.yml\">siguiente flujo de trabajo<\/a> publicar\u00e1 el plugin de extensi\u00f3n de WordPress en el directorio:<\/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>Este flujo de trabajo utiliza la acci\u00f3n <a href=\"https:\/\/github.com\/10up\/action-wordpress-plugin-deploy\" target=\"_blank\" rel=\"noopener noreferrer\"><code>10up\/action-wordpress-plugin-deploy<\/code><\/a> que recupera el c\u00f3digo de un repositorio Git y lo env\u00eda al <a href=\"https:\/\/kinsta.com\/es\/blog\/directorio-de-plugins-de-wordpress\/#plugin-structure\">repositorio SVN de WordPress.org<\/a>, simplificando la operaci\u00f3n.<\/p>\n<h2>Resumen<\/h2>\n<p>Al crear un plugin extensible para WordPress, nuestro objetivo es facilitar al m\u00e1ximo su ampliaci\u00f3n por parte de desarrolladores externos, maximizando as\u00ed las posibilidades de fomentar un ecosistema vibrante en torno a nuestros plugins.<\/p>\n<p>Aunque proporcionar una amplia documentaci\u00f3n puede guiar a los desarrolladores sobre c\u00f3mo ampliar el plugin, un enfoque a\u00fan m\u00e1s eficaz es proporcionar el c\u00f3digo PHP y las herramientas necesarias para desarrollar, probar y publicar sus extensiones.<\/p>\n<p>Al incluir el c\u00f3digo adicional que necesitan las extensiones directamente en nuestro plugin, simplificamos el proceso para los desarrolladores.<\/p>\n<p><em>\u00bfPlaneas hacer extensible tu plugin de WordPress? H\u00e1znoslo saber en la secci\u00f3n de comentarios.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Los plugins de WordPress pueden ampliarse con funciones adicionales, como demuestran plugins populares como WooCommerce y Gravity Forms. En el art\u00edculo \u00abDise\u00f1ar un plugin de WordPress &#8230;<\/p>\n","protected":false},"author":196,"featured_media":75478,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_kinsta_gated_content":false,"_kinsta_gated_content_redirect":"","footnotes":""},"tags":[],"topic":[1345,1352],"class_list":["post-75477","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","topic-desarrollo-wordpress","topic-plugins-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>C\u00f3mo hacer extensible un plugin de WordPress con clases PHP - Kinsta\u00ae<\/title>\n<meta name=\"description\" content=\"Aprende a crear un plugin de WordPress extensible que simplifique el proceso de desarrollo para desarrolladores externos.\" \/>\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\/es\/blog\/plugin-extensible-wordpress\/\" \/>\n<meta property=\"og:locale\" content=\"es_ES\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"C\u00f3mo hacer extensible un plugin de WordPress con clases PHP\" \/>\n<meta property=\"og:description\" content=\"Aprende a crear un plugin de WordPress extensible que simplifique el proceso de desarrollo para desarrolladores externos.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/\" \/>\n<meta property=\"og:site_name\" content=\"Kinsta\u00ae\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/kinsta.es\/\" \/>\n<meta property=\"article:published_time\" content=\"2024-06-19T09:53:42+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-06-28T07:58:08+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/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=\"Aprende a crear un plugin de WordPress extensible que simplifique el proceso de desarrollo para desarrolladores externos.\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/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_ES\" \/>\n<meta name=\"twitter:label1\" content=\"Escrito por\" \/>\n\t<meta name=\"twitter:data1\" content=\"Leonardo Losoviz\" \/>\n\t<meta name=\"twitter:label2\" content=\"Tiempo de lectura\" \/>\n\t<meta name=\"twitter:data2\" content=\"17 minutos\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/\"},\"author\":{\"name\":\"Leonardo Losoviz\",\"@id\":\"https:\/\/kinsta.com\/es\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238\"},\"headline\":\"C\u00f3mo hacer extensible un plugin de WordPress con clases PHP\",\"datePublished\":\"2024-06-19T09:53:42+00:00\",\"dateModified\":\"2024-06-28T07:58:08+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/\"},\"wordCount\":1655,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/kinsta.com\/es\/#organization\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg\",\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/\",\"url\":\"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/\",\"name\":\"C\u00f3mo hacer extensible un plugin de WordPress con clases PHP - Kinsta\u00ae\",\"isPartOf\":{\"@id\":\"https:\/\/kinsta.com\/es\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg\",\"datePublished\":\"2024-06-19T09:53:42+00:00\",\"dateModified\":\"2024-06-28T07:58:08+00:00\",\"description\":\"Aprende a crear un plugin de WordPress extensible que simplifique el proceso de desarrollo para desarrolladores externos.\",\"breadcrumb\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#breadcrumb\"},\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#primaryimage\",\"url\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg\",\"contentUrl\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg\",\"width\":1460,\"height\":730},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/kinsta.com\/es\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Plugins de WordPress\",\"item\":\"https:\/\/kinsta.com\/es\/secciones\/plugins-wordpress\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"C\u00f3mo hacer extensible un plugin de WordPress con clases PHP\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/kinsta.com\/es\/#website\",\"url\":\"https:\/\/kinsta.com\/es\/\",\"name\":\"Kinsta\u00ae\",\"description\":\"Soluciones de alojamiento premium, r\u00e1pidas y seguras\",\"publisher\":{\"@id\":\"https:\/\/kinsta.com\/es\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/kinsta.com\/es\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"es\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/kinsta.com\/es\/#organization\",\"name\":\"Kinsta\",\"url\":\"https:\/\/kinsta.com\/es\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/kinsta.com\/es\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/12\/kinsta-logo.jpeg\",\"contentUrl\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/12\/kinsta-logo.jpeg\",\"width\":500,\"height\":500,\"caption\":\"Kinsta\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/es\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/kinsta.es\/\",\"https:\/\/x.com\/Kinsta_ES\",\"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\/es\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238\",\"name\":\"Leonardo Losoviz\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/kinsta.com\/es\/#\/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\/es\/blog\/author\/leonardolosoviz\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"C\u00f3mo hacer extensible un plugin de WordPress con clases PHP - Kinsta\u00ae","description":"Aprende a crear un plugin de WordPress extensible que simplifique el proceso de desarrollo para desarrolladores externos.","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\/es\/blog\/plugin-extensible-wordpress\/","og_locale":"es_ES","og_type":"article","og_title":"C\u00f3mo hacer extensible un plugin de WordPress con clases PHP","og_description":"Aprende a crear un plugin de WordPress extensible que simplifique el proceso de desarrollo para desarrolladores externos.","og_url":"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/","og_site_name":"Kinsta\u00ae","article_publisher":"https:\/\/www.facebook.com\/kinsta.es\/","article_published_time":"2024-06-19T09:53:42+00:00","article_modified_time":"2024-06-28T07:58:08+00:00","og_image":[{"width":1460,"height":730,"url":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/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":"Aprende a crear un plugin de WordPress extensible que simplifique el proceso de desarrollo para desarrolladores externos.","twitter_image":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions-1024x512.jpg","twitter_creator":"@losoviz","twitter_site":"@Kinsta_ES","twitter_misc":{"Escrito por":"Leonardo Losoviz","Tiempo de lectura":"17 minutos"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#article","isPartOf":{"@id":"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/"},"author":{"name":"Leonardo Losoviz","@id":"https:\/\/kinsta.com\/es\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238"},"headline":"C\u00f3mo hacer extensible un plugin de WordPress con clases PHP","datePublished":"2024-06-19T09:53:42+00:00","dateModified":"2024-06-28T07:58:08+00:00","mainEntityOfPage":{"@id":"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/"},"wordCount":1655,"commentCount":0,"publisher":{"@id":"https:\/\/kinsta.com\/es\/#organization"},"image":{"@id":"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#primaryimage"},"thumbnailUrl":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg","inLanguage":"es","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/","url":"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/","name":"C\u00f3mo hacer extensible un plugin de WordPress con clases PHP - Kinsta\u00ae","isPartOf":{"@id":"https:\/\/kinsta.com\/es\/#website"},"primaryImageOfPage":{"@id":"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#primaryimage"},"image":{"@id":"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#primaryimage"},"thumbnailUrl":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg","datePublished":"2024-06-19T09:53:42+00:00","dateModified":"2024-06-28T07:58:08+00:00","description":"Aprende a crear un plugin de WordPress extensible que simplifique el proceso de desarrollo para desarrolladores externos.","breadcrumb":{"@id":"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#breadcrumb"},"inLanguage":"es","potentialAction":[{"@type":"ReadAction","target":["https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/"]}]},{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#primaryimage","url":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg","contentUrl":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg","width":1460,"height":730},{"@type":"BreadcrumbList","@id":"https:\/\/kinsta.com\/es\/blog\/plugin-extensible-wordpress\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/kinsta.com\/es\/"},{"@type":"ListItem","position":2,"name":"Plugins de WordPress","item":"https:\/\/kinsta.com\/es\/secciones\/plugins-wordpress\/"},{"@type":"ListItem","position":3,"name":"C\u00f3mo hacer extensible un plugin de WordPress con clases PHP"}]},{"@type":"WebSite","@id":"https:\/\/kinsta.com\/es\/#website","url":"https:\/\/kinsta.com\/es\/","name":"Kinsta\u00ae","description":"Soluciones de alojamiento premium, r\u00e1pidas y seguras","publisher":{"@id":"https:\/\/kinsta.com\/es\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/kinsta.com\/es\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"es"},{"@type":"Organization","@id":"https:\/\/kinsta.com\/es\/#organization","name":"Kinsta","url":"https:\/\/kinsta.com\/es\/","logo":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/kinsta.com\/es\/#\/schema\/logo\/image\/","url":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/12\/kinsta-logo.jpeg","contentUrl":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2023\/12\/kinsta-logo.jpeg","width":500,"height":500,"caption":"Kinsta"},"image":{"@id":"https:\/\/kinsta.com\/es\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/kinsta.es\/","https:\/\/x.com\/Kinsta_ES","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\/es\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238","name":"Leonardo Losoviz","image":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/kinsta.com\/es\/#\/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\/es\/blog\/author\/leonardolosoviz\/"}]}},"acf":[],"_links":{"self":[{"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/posts\/75477","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/users\/196"}],"replies":[{"embeddable":true,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/comments?post=75477"}],"version-history":[{"count":9,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/posts\/75477\/revisions"}],"predecessor-version":[{"id":75600,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/posts\/75477\/revisions\/75600"}],"alternate":[{"embeddable":true,"hreflang":"en","title":"English","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/75477\/translations\/en"},{"embeddable":true,"hreflang":"it","title":"Italian","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/75477\/translations\/it"},{"embeddable":true,"hreflang":"pt","title":"Portuguese","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/75477\/translations\/pt"},{"embeddable":true,"hreflang":"fr","title":"French","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/75477\/translations\/fr"},{"embeddable":true,"hreflang":"de","title":"German","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/75477\/translations\/de"},{"embeddable":true,"hreflang":"ja","title":"Japanese","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/75477\/translations\/jp"},{"embeddable":true,"hreflang":"nl","title":"Dutch","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/75477\/translations\/nl"},{"embeddable":true,"hreflang":"es","title":"Spanish","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/75477\/translations\/es"},{"href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/75477\/tree"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/media\/75478"}],"wp:attachment":[{"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/media?parent=75477"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/tags?post=75477"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/topic?post=75477"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}