{"id":74018,"date":"2024-03-04T14:04:05","date_gmt":"2024-03-04T13:04:05","guid":{"rendered":"https:\/\/kinsta.com\/es\/?p=74018&#038;preview=true&#038;preview_id=74018"},"modified":"2024-03-06T13:59:19","modified_gmt":"2024-03-06T12:59:19","slug":"pro-versiones-libres-plugin-wordpress","status":"publish","type":"post","link":"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/","title":{"rendered":"Dise\u00f1ar un plugin de WordPress que admita extensiones"},"content":{"rendered":"<p>En el ecosistema de WordPress, la adopci\u00f3n de un <a href=\"https:\/\/kinsta.com\/es\/blog\/como-ganan-dinero-las-apps\/\">modelo freemium<\/a> es un m\u00e9todo predominante para promocionar y monetizar plugins comerciales. Este enfoque implica lanzar una versi\u00f3n b\u00e1sica del plugin de forma gratuita \u2014 normalmente a trav\u00e9s del <a href=\"https:\/\/wordpress.org\/plugins\/\" target=\"_blank\" rel=\"noopener noreferrer\">directorio de plugins de WordPress<\/a> \u2014 y ofrecer funciones mejoradas a trav\u00e9s de una versi\u00f3n PRO o add-ons, que suelen venderse en el sitio web del plugin.<\/p>\n<p>Hay tres formas diferentes de integrar funciones comerciales en un modelo freemium:<\/p>\n<ol>\n<li>Incluir estas funciones comerciales dentro del plugin gratuito, y activarlas s\u00f3lo cuando se instale la versi\u00f3n comercial en el sitio web o se proporcione una clave de licencia comercial.<\/li>\n<li>Crea las versiones gratuita y PRO como plugins independientes, con la versi\u00f3n PRO dise\u00f1ada para sustituir a la versi\u00f3n gratuita, garantizando que s\u00f3lo haya una versi\u00f3n instalada en cada momento.<\/li>\n<li>Instalar la versi\u00f3n PRO junto al plugin gratuito, ampliando su funcionalidad. Esto requiere que ambas versiones est\u00e9n presentes.<\/li>\n<\/ol>\n<p>Sin embargo, el primer enfoque es <a href=\"https:\/\/developer.wordpress.org\/plugins\/wordpress-org\/detailed-plugin-guidelines\/#5-trialware-is-not-permitted\" target=\"_blank\" rel=\"noopener noreferrer\">incompatible con las directrices<\/a> para plugins distribuidos a trav\u00e9s del directorio de plugins de WordPress, ya que estas normas proh\u00edben la inclusi\u00f3n de funciones restringidas o bloqueadas hasta que se realice un pago o una actualizaci\u00f3n.<\/p>\n<p>Esto nos deja con las dos \u00faltimas opciones, que ofrecen ventajas e inconvenientes. Las siguientes secciones explican por qu\u00e9 la \u00faltima estrategia, \u00abPRO sobre gratis\u00bb, es nuestra mejor opci\u00f3n.<\/p>\n<p>Vamos a profundizar en la segunda opci\u00f3n, \u00abPRO como sustituto de gratis\u00bb, sus defectos y por qu\u00e9, en definitiva, no es recomendable.<\/p>\n<p>Posteriormente, exploramos en profundidad la \u00abPRO sobre gratis\u00bb, destacando por qu\u00e9 se erige como la opci\u00f3n preferida.<\/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>Ventajas de la estrategia \u00abPRO como sustituto de gratis\u00bb<\/h2>\n<p>La estrategia \u00abPRO como sustituto de gratis\u00bb es relativamente f\u00e1cil de aplicar porque los desarrolladores pueden utilizar un \u00fanico c\u00f3digo base para ambos plugins (free y PRO) y crear dos productos a partir de \u00e9l, con la versi\u00f3n gratuita (o \u00abest\u00e1ndar\u00bb) incluyendo simplemente un subconjunto del c\u00f3digo, y la versi\u00f3n PRO incluyendo todo el c\u00f3digo.<\/p>\n<p>Por ejemplo, el c\u00f3digo base del proyecto podr\u00eda dividirse en los directorios <code>standard\/<\/code> y <code>pro\/<\/code>. El plugin cargar\u00eda siempre el c\u00f3digo est\u00e1ndar, y el c\u00f3digo PRO se cargar\u00eda condicionalmente, en funci\u00f3n de la presencia del directorio respectivo:<\/p>\n<pre><code class=\"language-php\">\/\/ Main plugin file: myplugin.php\n\n\/\/ Always load the standard plugin's code\nrequire_once __DIR__ . '\/standard\/load.php';\n\n\/\/ Load the PRO plugin's code only if the folder exists\n$proFolder = __DIR__ . '\/pro';\nif (file_exists($proFolder)) {\n  require_once $proFolder . '\/load.php';\n}\n<\/code><\/pre>\n<p>Entonces, al generar el plugin mediante una herramienta de <a href=\"https:\/\/kinsta.com\/es\/blog\/como-configurar-pipeline-ci-cd\/\">Integraci\u00f3n Continua<\/a>, podemos crear los dos activos <code>myplugin-standard.zip<\/code> y <code>myplugin-pro.zip<\/code> a partir del mismo c\u00f3digo fuente.<\/p>\n<p>Si alojas el proyecto en GitHub y generas los activos mediante Acciones de GitHub, el siguiente flujo de trabajo realiza el trabajo:<\/p>\n<pre><code class=\"language-yaml\">name: Generate the standard and PRO plugins\non:\n  release:\n    types: [published]\n\njobs:\n  process:\n    name: Generate plugins\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions\/checkout@v3\n\n      - name: Install zip\n        uses: montudor\/action-zip@v1.0.0\n\n      - name: Create the standard plugin .zip file (excluding all PRO code)\n        run: zip -X -r myplugin-standard.zip . -x **\/src\/pro\/*\n\n      - name: Create the PRO plugin .zip file\n        run: zip -X -r myplugin-pro.zip . -x myplugin-standard.zip\n\n      - name: Upload both plugins to the release page\n        uses: softprops\/action-gh-release@v1\n        with:\n          files: |\n            myplugin-standard.zip\n            myplugin-pro.zip\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n<\/code><\/pre>\n<h2>Problemas con la estrategia \u00abPRO como sustituto de gratis\u00bb<\/h2>\n<p>La estrategia \u00abPRO como sustituto de gratis\u00bb exige sustituir el plugin gratuito por la versi\u00f3n PRO. Como consecuencia, si el plugin gratuito se distribuye a trav\u00e9s del directorio de plugins de WordPress, su recuento de \u00abinstalaciones activas\u00bb descender\u00e1 (ya que s\u00f3lo rastrea el plugin gratuito, no la versi\u00f3n PRO), dando la impresi\u00f3n de que el plugin es menos popular de lo que realmente es.<\/p>\n<p>Este resultado anular\u00eda el prop\u00f3sito de utilizar el directorio de plugins de WordPress en primer lugar: Como un canal de descubrimiento de plugins en el que los usuarios pueden conocer nuestro plugin, descargarlo e instalarlo. (Despu\u00e9s, una vez instalado, el plugin gratuito puede invitar a los usuarios a actualizarse a la versi\u00f3n PRO).<\/p>\n<p>Si el n\u00famero de instalaciones activas no es elevado, es posible que los usuarios no se convenzan de instalar nuestro plugin. Como ejemplo de c\u00f3mo las cosas pueden ir mal, los propietarios del plugin Newsletter Glue decidieron <a href=\"https:\/\/wptavern.com\/newsletter-glue-closes-free-plugin-on-wordpress-org\" target=\"_blank\" rel=\"noopener noreferrer\">eliminar el plugin del directorio de plugins de WordPress<\/a>, ya que el bajo recuento de activaciones estaba perjudicando las posibilidades del plugin.<\/p>\n<p>Como la estrategia \u00abPRO como sustituto de gratis\u00bb no es viable, s\u00f3lo nos queda una opci\u00f3n: la estrategia \u00abPRO sobre gratuito\u00bb.<\/p>\n<p>Exploremos los entresijos de esta estrategia.<\/p>\n<h2>Conceptualizaci\u00f3n de la estrategia \u00abPRO sobre gratis\u00bb<\/h2>\n<p>La idea es que el plugin gratuito se instala en el sitio, y su funcionalidad puede ampliarse instalando plugins o addons adicionales. Puede ser mediante un \u00fanico plugin PRO o mediante una colecci\u00f3n de extensiones o addons PRO, cada uno de los cuales proporciona una funcionalidad espec\u00edfica.<\/p>\n<p>En esta situaci\u00f3n, al plugin gratuito no le importa qu\u00e9 otros plugins est\u00e1n instalados en el sitio. Lo \u00fanico que hace es proporcionar una funcionalidad adicional. Este modelo es vers\u00e1til, ya que permite la expansi\u00f3n tanto por parte de los desarrolladores originales como de terceros creadores, fomentando un ecosistema en el que un plugin puede evolucionar en direcciones imprevistas.<\/p>\n<p>Por favor, f\u00edjate en que no importa si las extensiones PRO ser\u00e1n producidas por nosotros (es decir, los mismos desarrolladores que construyen el plugin est\u00e1ndar), o por alguien m\u00e1s: El c\u00f3digo para tratar con ambos es el mismo. Por lo tanto, es una buena idea crear una base que no restrinja c\u00f3mo se puede ampliar el plugin. Esto har\u00e1 posible que los desarrolladores de terceros ampl\u00eden nuestro plugin de formas que no hab\u00edamos concebido.<\/p>\n<h2>Enfoques de dise\u00f1o: hooks y contenedores de servicios<\/h2>\n<p>Hay dos enfoques principales para hacer extensible el c\u00f3digo PHP:<\/p>\n<ol>\n<li>A trav\u00e9s de los <a href=\"https:\/\/kinsta.com\/es\/blog\/wordpress-hooks\/\">hooks de acci\u00f3n y filtro<\/a> de WordPress<\/li>\n<li>A trav\u00e9s de un <a href=\"https:\/\/kinsta.com\/es\/blog\/abstraccion-en-wordpress-plugin\/#accessing-services-via-the-service-container\">contenedor de servicios<\/a><\/li>\n<\/ol>\n<p>El primer enfoque es el m\u00e1s com\u00fan entre los desarrolladores de WordPress, mientras que el segundo es el preferido por la <a href=\"https:\/\/kinsta.com\/php\/\">comunidad PHP<\/a> en general.<\/p>\n<p>Veamos ejemplos de ambos.<\/p>\n<h2>Hacer extensible el c\u00f3digo mediante hooks de acciones y filtros<\/h2>\n<p>WordPress ofrece <a href=\"https:\/\/developer.wordpress.org\/plugins\/hooks\/\" target=\"_blank\" rel=\"noopener noreferrer\">hooks<\/a> (filtros y acciones) como mecanismo para modificar el comportamiento. Los hooks de filtro se utilizan para anular valores, y los hooks de acci\u00f3n para ejecutar funcionalidades personalizadas.<\/p>\n<p>Nuestro plugin principal puede entonces estar \u00abplagado\u00bb de hooks por toda su base de c\u00f3digo, permitiendo a los desarrolladores modificar su comportamiento.<\/p>\n<p>Un buen ejemplo de esto es <a href=\"https:\/\/kinsta.com\/es\/blog\/tutorial-de-woocommerce\/\">WooCommerce<\/a>, que ha extendido un enorme ecosistema de add-ons, la mayor\u00eda de los cuales pertenecen a proveedores de terceros. Esto es posible gracias al <a href=\"https:\/\/woocommerce.github.io\/code-reference\/hooks\/hooks.html\" target=\"_blank\" rel=\"noopener noreferrer\">amplio n\u00famero de hooks<\/a>\u00a0que ofrece este plugin.<\/p>\n<p>Los desarrolladores de WooCommerce han a\u00f1adido hooks a prop\u00f3sito, aunque ellos mismos no los necesiten. Es para que otro los utilice. F\u00edjate en el gran n\u00famero de hooks de acci\u00f3n \u00abbefore\u00bb(antes) y \u00abafter\u00bb(despu\u00e9s):<\/p>\n<ul>\n<li><code>woocommerce_after_account_downloads<\/code><\/li>\n<li><code>woocommerce_after_account_navigation<\/code><\/li>\n<li><code>woocommerce_after_account_orders<\/code><\/li>\n<li><code>woocommerce_after_account_payment_methods<\/code><\/li>\n<li><code>woocommerce_after_available_downloads<\/code><\/li>\n<li><code>woocommerce_after_cart<\/code><\/li>\n<li><code>woocommerce_after_cart_contents<\/code><\/li>\n<li><code>woocommerce_after_cart_item_name<\/code><\/li>\n<li><code>woocommerce_after_cart_table<\/code><\/li>\n<li><code>woocommerce_after_cart_totals<\/code><\/li>\n<li>&#8230;<\/li>\n<li><code>woocommerce_before_account_downloads<\/code><\/li>\n<li><code>woocommerce_before_account_navigation<\/code><\/li>\n<li><code>woocommerce_before_account_orders<\/code><\/li>\n<li><code>woocommerce_before_account_orders_pagination<\/code><\/li>\n<li><code>woocommerce_before_account_payment_methods<\/code><\/li>\n<li><code>woocommerce_before_available_downloads<\/code><\/li>\n<li><code>woocommerce_before_cart<\/code><\/li>\n<li><code>woocommerce_before_cart_collaterals<\/code><\/li>\n<li><code>woocommerce_before_cart_contents<\/code><\/li>\n<li><code>woocommerce_before_cart_table<\/code><\/li>\n<li><code>woocommerce_before_cart_totals<\/code><\/li>\n<li>&#8230;<\/li>\n<\/ul>\n<p>Por ejemplo, el archivo <a href=\"https:\/\/woocommerce.github.io\/code-reference\/files\/woocommerce-templates-myaccount-downloads.html#source-view.27\" target=\"_blank\" rel=\"noopener noreferrer\"><code>downloads.php<\/code><\/a> contiene varias acciones para inyectar funcionalidad adicional, y la URL de la tienda puede anularse mediante un filtro:<\/p>\n<pre><code class=\"language-php\">&lt;?php\n\n$downloads     = WC()-&gt;customer-&gt;get_downloadable_products();\n$has_downloads = (bool) $downloads;\n\ndo_action( 'woocommerce_before_account_downloads', $has_downloads ); ?&gt;\n\n&lt;?php if ( $has_downloads ) : ?&gt;\n\n  &lt;?php do_action( 'woocommerce_before_available_downloads' ); ?&gt;\n\n  &lt;?php do_action( 'woocommerce_available_downloads', $downloads ); ?&gt;\n\n  &lt;?php do_action( 'woocommerce_after_available_downloads' ); ?&gt;\n\n&lt;?php else : ?&gt;\n\n  &lt;?php\n\n  $wp_button_class = wc_wp_theme_get_element_class_name( 'button' ) ? ' ' . wc_wp_theme_get_element_class_name( 'button' ) : '';\n  wc_print_notice( esc_html__( 'No downloads available yet.', 'woocommerce' ) . ' &lt;a class=\"button wc-forward' . esc_attr( $wp_button_class ) . '\" href=\"' . esc_url( apply_filters( 'woocommerce_return_to_shop_redirect', wc_get_page_permalink( 'shop' ) ) ) . '\"&gt;' . esc_html__( 'Browse products', 'woocommerce' ) . '&lt;\/a&gt;', 'notice' );\n  ?&gt;\n\n&lt;?php endif; ?&gt;\n\n&lt;?php do_action( 'woocommerce_after_account_downloads', $has_downloads ); ?&gt;\n<\/code><\/pre>\n<h2>Hacer extensible el c\u00f3digo mediante contenedores de servicio<\/h2>\n<p>Un contenedor de servicios es un objeto PHP que nos ayuda a gestionar la instanciaci\u00f3n de todas las clases del proyecto, y que suele ofrecerse como parte de una biblioteca de \u00abinyecci\u00f3n de dependencias\u00bb.<\/p>\n<p>La <a href=\"https:\/\/kinsta.com\/es\/blog\/abstraccion-en-wordpress-plugin\/#using-dependency-injection\">inyecci\u00f3n de dependencias<\/a> es una estrategia que permite unir todas las partes de la aplicaci\u00f3n de forma descentralizada: Las clases PHP se inyectan en la aplicaci\u00f3n a trav\u00e9s de la configuraci\u00f3n, y la aplicaci\u00f3n recupera instancias de estas clases PHP a trav\u00e9s del contenedor de servicios.<\/p>\n<p>Hay muchas bibliotecas de inyecci\u00f3n de dependencias disponibles. Las siguientes son las m\u00e1s populares y son intercambiables, ya que todas satisfacen la <a href=\"https:\/\/www.php-fig.org\/psr\/psr-11\/\" target=\"_blank\" rel=\"noopener noreferrer\">PSR-11<\/a> (recomendaci\u00f3n del est\u00e1ndar PHP) que describe los contenedores de inyecci\u00f3n de dependencias:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/symfony\/dependency-injection\" target=\"_blank\" rel=\"noopener noreferrer\">DependencyInjection de Symfony<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/PHP-DI\/PHP-DI\" target=\"_blank\" rel=\"noopener noreferrer\">PHP-DI<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/silexphp\/Pimple\" target=\"_blank\" rel=\"noopener noreferrer\">Pimple<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/thephpleague\/container\" target=\"_blank\" rel=\"noopener noreferrer\">Container (Dependency Injection)<\/a><\/li>\n<\/ul>\n<p><a href=\"https:\/\/kinsta.com\/es\/blog\/que-es-laravel\/\">Laravel<\/a> tambi\u00e9n contiene un <a href=\"https:\/\/laravel.com\/docs\/10.x\/container\">contenedor de servicios<\/a> que ya est\u00e1 incorporado en la aplicaci\u00f3n.<\/p>\n<p>Utilizando la inyecci\u00f3n de dependencias, el plugin gratuito no necesita saber previamente qu\u00e9 clases PHP est\u00e1n presentes en la fase de ejecuci\u00f3n: Simplemente solicita instancias de todas las clases al contenedor de servicios. Mientras que muchas clases PHP son proporcionadas por el propio plugin gratuito para satisfacer su funcionalidad, otras son proporcionadas por cualquier complemento instalado en el sitio para ampliar la funcionalidad.<\/p>\n<p>Un buen ejemplo de uso de un contenedor de servicios es <a href=\"https:\/\/wordpress.org\/plugins\/gatographql\/\" target=\"_blank\" rel=\"noopener noreferrer\">Gato GraphQL<\/a>, que se basa en la biblioteca DependencyInjection de Symfony.<\/p>\n<p>As\u00ed se <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/ce461a7155782823b9d14383e1036a75b564807c\/layers\/Engine\/packages\/root\/src\/Container\/ContainerBuilderFactoryTrait.php\">instanciar\u00e1 el contenedor de servicios<\/a>:<\/p>\n<pre><code class=\"language-php\">&lt;?php\n\ndeclare(strict_types=1);\n\nnamespace GatoGraphQL\\Container;\n\nuse Symfony\\Component\\Config\\ConfigCache;\nuse Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface;\nuse Symfony\\Component\\DependencyInjection\\Dumper\\PhpDumper;\n\ntrait ContainerBuilderFactoryTrait\n{\n  protected ContainerInterface $instance;\n  protected bool $cacheContainerConfiguration;\n  protected bool $cached;\n  protected string $cacheFile;\n\n  \/**\n   * Initialize the Container Builder.\n   * If the directory is not provided, store the\n   * cache in a system temp dir\n   *\/\n  public function init(\n    bool $cacheContainerConfiguration,\n    string $namespace,\n    string $directory\n  ): void {\n    $this-&gt;cacheContainerConfiguration = $cacheContainerConfiguration;\n\n    if ($this-&gt;cacheContainerConfiguration) {\n      if (!$directory) {\n        $directory = sys_get_temp_dir() . \\DIRECTORY_SEPARATOR . 'container-cache';\n      }\n      $directory .= \\DIRECTORY_SEPARATOR . $namespace;\n      if (!is_dir($directory)) {\n        @mkdir($directory, 0777, true);\n      }\n      \n      \/\/ Store the cache under this file\n      $this-&gt;cacheFile = $directory . 'container.php';\n\n      $containerConfigCache = new ConfigCache($this-&gt;cacheFile, false);\n      $this-&gt;cached = $containerConfigCache-&gt;isFresh();\n    } else {\n      $this-&gt;cached = false;\n    }\n\n    \/\/ If not cached, then create the new instance\n    if (!$this-&gt;cached) {\n      $this-&gt;instance = new ContainerBuilder();\n    } else {\n      require_once $this-&gt;cacheFile;\n      \/** @var class-string&lt;ContainerBuilder&gt; *\/\n      $containerFullyQuantifiedClass = \"\\\\GatoGraphQL\\\\ServiceContainer\";\n      $this-&gt;instance = new $containerFullyQuantifiedClass();\n    }\n  }\n\n  public function getInstance(): ContainerInterface\n  {\n    return $this-&gt;instance;\n  }\n\n  \/**\n   * If the container is not cached, then compile it and cache it\n   *\n   * @param CompilerPassInterface[] $compilerPasses Compiler Pass objects to register on the container\n   *\/\n  public function maybeCompileAndCacheContainer(\n    array $compilerPasses = []\n  ): void {\n    \/**\n     * Compile Symfony's DependencyInjection Container Builder.\n     *\n     * After compiling, cache it in disk for performance.\n     *\n     * This happens only the first time the site is accessed\n     * on the current server.\n     *\/\n    if ($this-&gt;cached) {\n      return;\n    }\n\n    \/** @var ContainerBuilder *\/\n    $containerBuilder = $this-&gt;getInstance();\n    foreach ($compilerPasses as $compilerPass) {\n      $containerBuilder-&gt;addCompilerPass($compilerPass);\n    }\n\n    \/\/ Compile the container.\n    $containerBuilder-&gt;compile();\n\n    \/\/ Cache the container\n    if (!$this-&gt;cacheContainerConfiguration) {\n      return;\n    }\n    \n    \/\/ Create the folder if it doesn't exist, and check it was successful\n    $dir = dirname($this-&gt;cacheFile);\n    $folderExists = file_exists($dir);\n    if (!$folderExists) {\n      $folderExists = @mkdir($dir, 0777, true);\n      if (!$folderExists) {\n        return;\n      }\n    }\n\n    \/\/ Save the container to disk\n    $dumper = new PhpDumper($containerBuilder);\n    file_put_contents(\n      $this-&gt;cacheFile,\n      $dumper-&gt;dump(\n        [\n          'class' =&gt; 'ServiceContainer',\n          'namespace' =&gt; 'GatoGraphQL',\n        ]\n      )\n    );\n\n    \/\/ Change the permissions so it can be modified by external processes\n    chmod($this-&gt;cacheFile, 0777);\n  }\n}\n<\/code><\/pre>\n<p>Observa que el contenedor de servicios (accesible bajo el objeto PHP con la clase <code>GatoGraphQLServiceContainer<\/code>) se genera la primera vez que se ejecuta el plugin y luego se almacena en cach\u00e9 en el disco (como archivo <code>container.php<\/code> en una carpeta temporal del sistema). Esto se debe a que generar el contenedor de servicio es un proceso costoso que podr\u00eda tardar varios segundos en completarse.<\/p>\n<p>A continuaci\u00f3n, tanto el plugin principal como todas sus extensiones <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/fd73bf65cd30e164ffaa7860861974cf53683d51\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/config\/services.yaml\" target=\"_blank\" rel=\"noopener noreferrer\">definen qu\u00e9 servicios inyectar en el contenedor mediante un archivo de configuraci\u00f3n<\/a>:<\/p>\n<pre><code class=\"language-yaml\">services:\n  _defaults:\n    public: true\n    autowire: true\n    autoconfigure: true\n\n  GatoGraphQL\\GatoGraphQL\\Registries\\ModuleTypeRegistryInterface:\n    class: \\GatoGraphQL\\GatoGraphQL\\Registries\\ModuleTypeRegistry\n\n  GatoGraphQL\\GatoGraphQL\\Log\\LoggerInterface:\n    class: \\GatoGraphQL\\GatoGraphQL\\Log\\Logger\n\n  GatoGraphQL\\GatoGraphQL\\Services\\:\n    resource: ..\/src\/Services\/*\n\n  GatoGraphQL\\GatoGraphQL\\State\\:\n    resource: '..\/src\/State\/*'\n\n<\/code><\/pre>\n<p>Observa que podemos instanciar objetos para clases concretas (como <code>GatoGraphQL\\GatoGraphQL\\Log\\Logger<\/code>, a la que se accede a trav\u00e9s de su interfaz de contrato <code>GatoGraphQL\\GatoGraphQL\\Log\\LoggerInterface<\/code>), and ), y tambi\u00e9n podemos indicar \u00abinstanciar todas las clases bajo alg\u00fan directorio\u00bb (como todos los servicios bajo <code>..\/src\/Services<\/code>).<\/p>\n<p>Por \u00faltimo, <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/409eb230267021735e3326657c9929e94fa08b85\/layers\/Engine\/packages\/root\/src\/Module\/InitializeContainerServicesInModuleTrait.php\" target=\"_blank\" rel=\"noopener noreferrer\">inyectamos la configuraci\u00f3n en el contenedor de servicios<\/a>:<\/p>\n<pre><code class=\"language-php\">&lt;?php\n\ndeclare(strict_types=1);\n\nnamespace PoP\\Root\\Module;\n\nuse PoP\\Root\\App;\nuse Symfony\\Component\\Config\\FileLocator;\nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;\nuse Symfony\\Component\\DependencyInjection\\Loader\\YamlFileLoader;\n\ntrait InitializeContainerServicesInModuleTrait\n{\n  \/\/ Initialize the services defined in the YAML configuration file.\n  public function initServices(\n    string $dir,\n    string $serviceContainerConfigFileName\n  ): void {\n    \/\/ First check if the container has been cached. If so, do nothing\n    if (App::getContainerBuilderFactory()-&gt;isCached()) {\n      return;\n    }\n\n    \/\/ Initialize the ContainerBuilder with this module's service implementations\n    \/** @var ContainerBuilder *\/\n    $containerBuilder = App::getContainer();\n    $loader = new YamlFileLoader($containerBuilder, new FileLocator($dir));\n    $loader-&gt;load($serviceContainerConfigFileName);\n  }\n}\n<\/code><\/pre>\n<p>Los servicios inyectados en el contenedor pueden configurarse para que se inicialicen siempre o s\u00f3lo cuando se soliciten (modo perezoso).<\/p>\n<p>Por ejemplo, para representar un tipo de entrada personalizado, el plugin tiene la clase <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/04058ffae6700e3e34e529ebd709247d98106fed\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/src\/Services\/CustomPostTypes\/AbstractCustomPostType.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>AbstractCustomPostType<\/code><\/a>cuyo m\u00e9todo <code>initialize<\/code> ejecuta la l\u00f3gica para inicializarlo seg\u00fan WordPress:<\/p>\n<pre><code class=\"language-php\">&lt;?php\n\ndeclare(strict_types=1);\n\nnamespace GatoGraphQL\\GatoGraphQL\\Services\\CustomPostTypes;\n\nuse GatoGraphQL\\GatoGraphQL\\Services\\Taxonomies\\TaxonomyInterface;\nuse PoP\\Root\\Services\\AbstractAutomaticallyInstantiatedService;\n\nabstract class AbstractCustomPostType extends AbstractAutomaticallyInstantiatedService implements CustomPostTypeInterface\n{\n  public function initialize(): void\n  {\n    \\add_action(\n      'init',\n      $this-&gt;initCustomPostType(...)\n    );\n  }\n\n  \/**\n   * Register the post type\n   *\/\n  public function initCustomPostType(): void\n  {\n    \\register_post_type($this-&gt;getCustomPostType(), $this-&gt;getCustomPostTypeArgs());\n  }\n\n  abstract public function getCustomPostType(): string;\n\n  \/**\n   * Arguments for registering the post type\n   *\n   * @return array&lt;string,mixed&gt;\n   *\/\n  protected function getCustomPostTypeArgs(): array\n  {\n    \/** @var array&lt;string,mixed&gt; *\/\n    $postTypeArgs = [\n      'public' =&gt; $this-&gt;isPublic(),\n      'publicly_queryable' =&gt; $this-&gt;isPubliclyQueryable(),\n      'label' =&gt; $this-&gt;getCustomPostTypeName(),\n      'labels' =&gt; $this-&gt;getCustomPostTypeLabels($this-&gt;getCustomPostTypeName(), $this-&gt;getCustomPostTypePluralNames(true), $this-&gt;getCustomPostTypePluralNames(false)),\n      'capability_type' =&gt; 'post',\n      'hierarchical' =&gt; $this-&gt;isAPIHierarchyModuleEnabled() && $this-&gt;isHierarchical(),\n      'exclude_from_search' =&gt; true,\n      'show_in_admin_bar' =&gt; $this-&gt;showInAdminBar(),\n      'show_in_nav_menus' =&gt; true,\n      'show_ui' =&gt; true,\n      'show_in_menu' =&gt; true,\n      'show_in_rest' =&gt; true,\n    ];\n    return $postTypeArgs;\n  }\n\n  \/**\n   * Labels for registering the post type\n   *\n   * @param string $name_uc Singular name uppercase\n   * @param string $names_uc Plural name uppercase\n   * @param string $names_lc Plural name lowercase\n   * @return array&lt;string,string&gt;\n   *\/\n  protected function getCustomPostTypeLabels(string $name_uc, string $names_uc, string $names_lc): array\n  {\n    return array(\n      'name'         =&gt; $names_uc,\n      'singular_name'    =&gt; $name_uc,\n      'add_new'      =&gt; sprintf(\\__('Add New %s', 'gatographql'), $name_uc),\n      'add_new_item'     =&gt; sprintf(\\__('Add New %s', 'gatographql'), $name_uc),\n      'edit_item'      =&gt; sprintf(\\__('Edit %s', 'gatographql'), $name_uc),\n      'new_item'       =&gt; sprintf(\\__('New %s', 'gatographql'), $name_uc),\n      'all_items'      =&gt; $names_uc,\/\/sprintf(\\__('All %s', 'gatographql'), $names_uc),\n      'view_item'      =&gt; sprintf(\\__('View %s', 'gatographql'), $name_uc),\n      'search_items'     =&gt; sprintf(\\__('Search %s', 'gatographql'), $names_uc),\n      'not_found'      =&gt; sprintf(\\__('No %s found', 'gatographql'), $names_lc),\n      'not_found_in_trash' =&gt; sprintf(\\__('No %s found in Trash', 'gatographql'), $names_lc),\n      'parent_item_colon'  =&gt; sprintf(\\__('Parent %s:', 'gatographql'), $name_uc),\n    );\n  }\n}\n<\/code><\/pre>\n<p>Entonces, la clase <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/c870d8906ae1aec3c81acc039c53acc7aab5dff0\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/src\/Services\/CustomPostTypes\/GraphQLCustomEndpointCustomPostType.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>GraphQLCustomEndpointCustomPostType.php<\/code><\/a> es una implementaci\u00f3n de un tipo de entrada personalizado. Al ser inyectada como servicio en el contenedor, es instanciada y registrada en WordPress:<\/p>\n<pre><code class=\"language-php\">&lt;?php\n\ndeclare(strict_types=1);\n\nnamespace GatoGraphQL\\GatoGraphQL\\Services\\CustomPostTypes;\n\nclass GraphQLCustomEndpointCustomPostType extends AbstractCustomPostType\n{\n  public function getCustomPostType(): string\n  {\n    return 'graphql-endpoint';\n  }\n\n  protected function getCustomPostTypeName(): string\n  {\n    return \\__('GraphQL custom endpoint', 'gatographql');\n  }\n}\n<\/code><\/pre>\n<p>Esta clase est\u00e1 presente en el plugin gratuito y otras clases de tipo de publicaci\u00f3n personalizada, que se extienden de forma similar desde <code>AbstractCustomPostType<\/code>, son proporcionadas por las extensiones PRO.<\/p>\n<h2>Comparaci\u00f3n de hooks y contenedores de servicios<\/h2>\n<p>Comparemos los dos enfoques de dise\u00f1o.<\/p>\n<p>El lado positivo para los hooks de acci\u00f3n y filtro es que es el m\u00e9todo m\u00e1s sencillo, ya que su funcionalidad forma parte del core de WordPress. Y cualquier desarrollador que trabaje con WordPress ya sabe c\u00f3mo manejar los hooks, por lo que la curva de aprendizaje es baja.<\/p>\n<p>Sin embargo, su l\u00f3gica est\u00e1 ligada a un nombre de hook, que es una cadena, y, como tal, puede dar lugar a errores: Si se modifica el nombre del hook, se rompe la l\u00f3gica de la extensi\u00f3n. Sin embargo, el desarrollador puede no darse cuenta de que hay un problema porque el c\u00f3digo PHP sigue compil\u00e1ndose.<\/p>\n<p>En consecuencia, los hooks obsoletos tienden a mantenerse durante mucho tiempo en la base de c\u00f3digo, posiblemente incluso para siempre. El proyecto acumula entonces c\u00f3digo obsoleto que no puede eliminarse por miedo a romper las extensiones.<\/p>\n<p>Volviendo a WooCommerce, esta situaci\u00f3n se evidencia en el archivo <a href=\"https:\/\/woocommerce.github.io\/code-reference\/files\/woocommerce-templates-myaccount-dashboard.html#source-view.65\" target=\"_blank\" rel=\"noopener noreferrer\"><code>dashboard.php<\/code><\/a> (observa c\u00f3mo los hooks obsoletos se mantienen desde la versi\u00f3n <code>2.6<\/code>, mientras que la \u00faltima versi\u00f3n actual es <code>8.5<\/code>):<\/p>\n<pre><code class=\"language-php\">&lt;?php\n  \/**\n   * My Account dashboard.\n   *\n   * @since 2.6.0\n   *\/\n  do_action( 'woocommerce_account_dashboard' );\n\n  \/**\n   * Deprecated woocommerce_before_my_account action.\n   *\n   * @deprecated 2.6.0\n   *\/\n  do_action( 'woocommerce_before_my_account' );\n\n  \/**\n   * Deprecated woocommerce_after_my_account action.\n   *\n   * @deprecated 2.6.0\n   *\/\n  do_action( 'woocommerce_after_my_account' );\n<\/code><\/pre>\n<p>Utilizar un contenedor de servicios tiene el inconveniente de que requiere una biblioteca externa, lo que a\u00f1ade complejidad. A\u00fan m\u00e1s, <a href=\"https:\/\/gatographql.com\/blog\/graphql-api-for-wp-is-now-scoped-thanks-to-php-scoper\/#heading-php-scoper-the-easy-way-%F0%9F%98%8E\" target=\"_blank\" rel=\"noopener noreferrer\">esta biblioteca debe estar aislada<\/a> (utilizando <a href=\"https:\/\/github.com\/humbug\/php-scoper\" target=\"_blank\" rel=\"noopener noreferrer\">PHP-Scoper<\/a> o <a href=\"https:\/\/github.com\/BrianHenryIE\/strauss\" target=\"_blank\" rel=\"noopener noreferrer\">Strauss<\/a>) por miedo a que otra versi\u00f3n de la misma biblioteca sea instalada por otro plugin en el mismo sitio, lo que podr\u00eda producir conflictos.<\/p>\n<p>Utilizar un contenedor de servicios es, sin duda, m\u00e1s dif\u00edcil de implementar y requiere m\u00e1s tiempo de desarrollo.<\/p>\n<p>En el lado positivo, los contenedores de servicio tratan con clases PHP sin tener que acoplar la l\u00f3gica a alguna cadena. Esto hace que el proyecto utilice m\u00e1s <a href=\"https:\/\/phptherightway.com\/\" target=\"_blank\" rel=\"noopener noreferrer\">pr\u00e1cticas recomendadas<\/a> de PHP, lo que conduce a una base de c\u00f3digo m\u00e1s f\u00e1cil de mantener a largo plazo.<\/p>\n<h2>Resumen<\/h2>\n<p>Al crear un plugin para WordPress, es una buena idea que admita extensiones para permitirnos a nosotros (los creadores del plugin) ofrecer funciones comerciales y tambi\u00e9n a cualquier otra persona a\u00f1adir funcionalidades adicionales y, con suerte, abarcar un ecosistema centrado en el plugin.<\/p>\n<p>En este art\u00edculo, exploramos cu\u00e1les son las consideraciones relativas a la arquitectura del proyecto PHP para que el plugin sea extensible. Como hemos aprendido, podemos elegir entre dos enfoques de dise\u00f1o: utilizar hooks o utilizar un contenedor de servicios. Y comparamos ambos enfoques, identificando las virtudes y debilidades de cada uno.<\/p>\n<p><em>\u00bfPiensas hacer extensible tu plugin de WordPress? H\u00e1znoslo saber en la secci\u00f3n de comentarios.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>En el ecosistema de WordPress, la adopci\u00f3n de un modelo freemium es un m\u00e9todo predominante para promocionar y monetizar plugins comerciales. Este enfoque implica lanzar una &#8230;<\/p>\n","protected":false},"author":196,"featured_media":74019,"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-74018","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>Creaci\u00f3n de un plugin de WordPress compatible con extensiones - Kinsta\u00ae<\/title>\n<meta name=\"description\" content=\"Aprende a crear un plugin flexible y escalable que admita extensiones, mejora la funcionalidad y maximiza la compatibilidad.\" \/>\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\/pro-versiones-libres-plugin-wordpress\/\" \/>\n<meta property=\"og:locale\" content=\"es_ES\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Dise\u00f1ar un plugin de WordPress que admita extensiones\" \/>\n<meta property=\"og:description\" content=\"Aprende a crear un plugin flexible y escalable que admita extensiones, mejora la funcionalidad y maximiza la compatibilidad.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-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-03-04T13:04:05+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-03-06T12:59:19+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1460\" \/>\n\t<meta property=\"og:image:height\" content=\"730\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Leonardo Losoviz\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:description\" content=\"Aprende a crear un plugin flexible y escalable que admita extensiones, mejora la funcionalidad y maximiza la compatibilidad.\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg\" \/>\n<meta name=\"twitter:creator\" content=\"@losoviz\" \/>\n<meta name=\"twitter:site\" content=\"@Kinsta_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=\"13 minutos\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/\"},\"author\":{\"name\":\"Leonardo Losoviz\",\"@id\":\"https:\/\/kinsta.com\/es\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238\"},\"headline\":\"Dise\u00f1ar un plugin de WordPress que admita extensiones\",\"datePublished\":\"2024-03-04T13:04:05+00:00\",\"dateModified\":\"2024-03-06T12:59:19+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/\"},\"wordCount\":2072,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/kinsta.com\/es\/#organization\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg\",\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/\",\"url\":\"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/\",\"name\":\"Creaci\u00f3n de un plugin de WordPress compatible con extensiones - Kinsta\u00ae\",\"isPartOf\":{\"@id\":\"https:\/\/kinsta.com\/es\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg\",\"datePublished\":\"2024-03-04T13:04:05+00:00\",\"dateModified\":\"2024-03-06T12:59:19+00:00\",\"description\":\"Aprende a crear un plugin flexible y escalable que admita extensiones, mejora la funcionalidad y maximiza la compatibilidad.\",\"breadcrumb\":{\"@id\":\"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/#breadcrumb\"},\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/#primaryimage\",\"url\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg\",\"contentUrl\":\"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg\",\"width\":1460,\"height\":730},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-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\":\"Dise\u00f1ar un plugin de WordPress que admita extensiones\"}]},{\"@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":"Creaci\u00f3n de un plugin de WordPress compatible con extensiones - Kinsta\u00ae","description":"Aprende a crear un plugin flexible y escalable que admita extensiones, mejora la funcionalidad y maximiza la compatibilidad.","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\/pro-versiones-libres-plugin-wordpress\/","og_locale":"es_ES","og_type":"article","og_title":"Dise\u00f1ar un plugin de WordPress que admita extensiones","og_description":"Aprende a crear un plugin flexible y escalable que admita extensiones, mejora la funcionalidad y maximiza la compatibilidad.","og_url":"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/","og_site_name":"Kinsta\u00ae","article_publisher":"https:\/\/www.facebook.com\/kinsta.es\/","article_published_time":"2024-03-04T13:04:05+00:00","article_modified_time":"2024-03-06T12:59:19+00:00","og_image":[{"width":1460,"height":730,"url":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg","type":"image\/jpeg"}],"author":"Leonardo Losoviz","twitter_card":"summary_large_image","twitter_description":"Aprende a crear un plugin flexible y escalable que admita extensiones, mejora la funcionalidad y maximiza la compatibilidad.","twitter_image":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg","twitter_creator":"@losoviz","twitter_site":"@Kinsta_ES","twitter_misc":{"Escrito por":"Leonardo Losoviz","Tiempo de lectura":"13 minutos"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/#article","isPartOf":{"@id":"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/"},"author":{"name":"Leonardo Losoviz","@id":"https:\/\/kinsta.com\/es\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238"},"headline":"Dise\u00f1ar un plugin de WordPress que admita extensiones","datePublished":"2024-03-04T13:04:05+00:00","dateModified":"2024-03-06T12:59:19+00:00","mainEntityOfPage":{"@id":"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/"},"wordCount":2072,"commentCount":0,"publisher":{"@id":"https:\/\/kinsta.com\/es\/#organization"},"image":{"@id":"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/#primaryimage"},"thumbnailUrl":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg","inLanguage":"es","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/","url":"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/","name":"Creaci\u00f3n de un plugin de WordPress compatible con extensiones - Kinsta\u00ae","isPartOf":{"@id":"https:\/\/kinsta.com\/es\/#website"},"primaryImageOfPage":{"@id":"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/#primaryimage"},"image":{"@id":"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/#primaryimage"},"thumbnailUrl":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg","datePublished":"2024-03-04T13:04:05+00:00","dateModified":"2024-03-06T12:59:19+00:00","description":"Aprende a crear un plugin flexible y escalable que admita extensiones, mejora la funcionalidad y maximiza la compatibilidad.","breadcrumb":{"@id":"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/#breadcrumb"},"inLanguage":"es","potentialAction":[{"@type":"ReadAction","target":["https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/"]}]},{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-wordpress\/#primaryimage","url":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg","contentUrl":"https:\/\/kinsta.com\/es\/wp-content\/uploads\/sites\/8\/2024\/03\/wp-architecting-a-wordpress-plugin-to-support-extensions.jpg","width":1460,"height":730},{"@type":"BreadcrumbList","@id":"https:\/\/kinsta.com\/es\/blog\/pro-versiones-libres-plugin-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":"Dise\u00f1ar un plugin de WordPress que admita extensiones"}]},{"@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\/74018","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=74018"}],"version-history":[{"count":8,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/posts\/74018\/revisions"}],"predecessor-version":[{"id":74075,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/posts\/74018\/revisions\/74075"}],"alternate":[{"embeddable":true,"hreflang":"en","title":"English","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/74018\/translations\/en"},{"embeddable":true,"hreflang":"it","title":"Italian","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/74018\/translations\/it"},{"embeddable":true,"hreflang":"pt","title":"Portuguese","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/74018\/translations\/pt"},{"embeddable":true,"hreflang":"fr","title":"French","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/74018\/translations\/fr"},{"embeddable":true,"hreflang":"de","title":"German","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/74018\/translations\/de"},{"embeddable":true,"hreflang":"ja","title":"Japanese","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/74018\/translations\/jp"},{"embeddable":true,"hreflang":"es","title":"Spanish","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/74018\/translations\/es"},{"embeddable":true,"hreflang":"nl","title":"Dutch","href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/74018\/translations\/nl"},{"href":"https:\/\/kinsta.com\/es\/wp-json\/kinsta\/v1\/posts\/74018\/tree"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/media\/74019"}],"wp:attachment":[{"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/media?parent=74018"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/tags?post=74018"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/kinsta.com\/es\/wp-json\/wp\/v2\/topic?post=74018"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}