Usando register_activation_hook en clases

10 ago 2017, 16:36:08
Vistas: 22.2K
Votos: 10

Estoy tratando de desarrollar un plugin para propósitos básicos de SEO, ya que muchas personas que conozco no les gusta usar Yoast. Literalmente acabo de comenzar el plugin y estoy construyendo el mensaje de activación que se muestra al usuario cuando activan el plugin. Estoy teniendo problemas con una mezcla entre POO y funciones incorporadas de WordPress, y no estoy seguro de dónde me estoy equivocando.

La única forma en que puedo hacer que esto funcione es creando una nueva instancia de la clase SEO_Plugin_Activation dentro del constructor de mis archivos principales, y luego llamando al método activatePlugin en esa clase. Siento que esto es innecesario. También creo que la forma en que estoy ejecutando mis funciones en el archivo de la clase de activación no tiene mucho sentido tampoco. Pero es la única manera en que puedo hacer que funcione ahora mismo.

No estoy seguro si lo que estoy haciendo es porque no estoy comprendiendo las técnicas de POO al 100%, o si no estoy utilizando la API de WordPress correctamente. He incluido tres ejemplos de código en el siguiente orden:

  1. Archivo principal del plugin
  2. Archivo de clase que maneja mis requisitos de activación
  3. Lo que realmente esperaba que se pudiera hacer.

seo.php (archivo principal del plugin)

<?php
/*
... información genérica del plugin
*/

require_once(dirname(__FILE__) . '/admin/class-plugin-activation.php');

class SEO {
  function __construct() {
    $activate = new SEO_Plugin_Activation();
    $activate->activatePlugin();
  }

}

new SEO();
?>

class-plugin-activation.php

<?php
class SEO_Plugin_Activation {

  function __construct() {
    register_activation_hook(__FILE__ . '../seo.php', array($this, 'activatePlugin'));
    add_action('admin_notices', array($this, 'showSitemapInfo'));
  }

  function activatePlugin() {
    set_transient('show_sitemap_info', true, 5);
  }

  function showSitemapInfo() {
    if(get_transient('show_sitemap_info')) {
      echo '<div class="updated notice is-dismissible">' .
              'Los archivos de su mapa del sitio se pueden encontrar en los siguientes enlaces: ' .
            '</div>';
      delete_transient('show_sitemap_info');
    }
  }

}
?>

seo.php (Lo que esperaba)

<?php
/*
 ... bla bla bla
*/

require_once(dirname(__FILE__) . '/admin/class-plugin-activation.php');

class SEO {
  function __construct() {
    register_activation_hook(__FILE__, array($this, 'wp_install'));
  }

  function wp_install() {
    $activate = new SEO_Plugin_Activation();
    // Ejecutar algún método(s) aquí que se encargaría 
    // de todo el arranque de activación de mi plugin
  }

}

new SEO();
?>

Intenté hacerlo de la manera que describo en el tercer script, pero no tengo éxito. Por ahora, el mensaje se muestra correctamente, sin mensajes de error.

4
Comentarios

¿Estás buscando tranquilidad o la forma correcta de hacer OO en WordPress? Me temo que no hay un camino canónico a seguir aquí, lo único que puedo compartir son cosas genéricas de OO. Por ejemplo, no crees SEO_Plugin_Activation dentro de tu clase SEO, emplea la inyección de dependencias y pásalo como argumento en su lugar, y no definas y uses una clase en el mismo archivo, cargar un archivo que describe una clase no debería también ejecutarla, de lo contrario es imposible escribir pruebas unitarias

Tom J Nowell Tom J Nowell
10 ago 2017 16:47:12

Gracias por la respuesta. Honestamente no me importa demasiado ceñirme al "100% Wordpress Way". A juzgar por tu comentario, parece que claramente me falta algo de comprensión de los principios de OOP. Tenía la impresión de que necesitaba crear una nueva clase dentro de una clase para llamar a sus métodos.

Dan Zuzevich Dan Zuzevich
10 ago 2017 16:50:59

Puedes hacer eso, pero no es tan flexible, por ejemplo, si quieres probar la clase SEO, ¿cómo reemplazarías la clase SEO_Plugin_Activation con un objeto simulado sin modificarla? De todos modos parece que esta pregunta surge de un malentendido sobre cómo funciona register_activation_hook

Tom J Nowell Tom J Nowell
10 ago 2017 17:04:04

Entendido, muchas gracias. Sinceramente, no creo que vaya a realizar pruebas en nada porque eso está un poco fuera de mi alcance por ahora.

Dan Zuzevich Dan Zuzevich
10 ago 2017 17:21:13
Todas las respuestas a la pregunta 4
6
14

Después de releer tu pregunta, creo que entiendo el problema, y surge de un malentendido sobre cómo funciona register_activation_hook, combinado con cierta confusión sobre cómo estás iniciando tu código y qué significa iniciar.

Parte 1: register_activation_hook

Esta función toma 2 parámetros:

register_activation_hook( string $file, callable $function )

El primer parámetro, $file es el archivo principal del plugin, no el archivo que contiene lo que quieres ejecutar. Se usa de la misma manera que plugins_url, por lo que necesitas el valor de __FILE__, específicamente su valor en el archivo raíz del plugin con tu encabezado de plugin.

El segundo parámetro es un callable y funciona como esperas, pero en realidad solo está usando add_action internamente

Cuando un plugin se activa, se llama al hook 'activate_NOMBREDELPLUGIN'. En el nombre de este hook, NOMBREDELPLUGIN se reemplaza con el nombre del plugin, incluyendo el subdirectorio opcional. Por ejemplo, cuando el plugin está ubicado en wp-content/plugins/pluginEjemplo/ejemplo.php, el nombre de este hook será 'activate_pluginEjemplo/ejemplo.php'.

Parte 2: Inicio y __FILE__

Un problema fundamental aquí es que __FILE__ tendrá valores diferentes en diferentes ubicaciones, y necesitas un valor específico.

También tienes un problema, que el inicio debería ensamblar el gráfico de objetos, pero no haces eso. Todos tus objetos se crean tan pronto como se definen, o se crean dentro de otros, lo que dificulta o imposibilita pasarles valores.

Como ejemplo, podría escribir un plugin así:

plugin.php:

<?php
/**
 * Plugin Name: Mi Plugin
 * Version: 0.1
 */

// paso de carga
require_once( 'php/app.php' );

// paso de inicio
$app = new App( __FILE__ );

// paso de ejecución
$app->run();

Definí todas mis clases en la subcarpeta php, y las cargué todas en el mismo lugar. PHP ahora sabe qué son mis clases, sus nombres, etc., pero aún no ha pasado nada.

Luego creo todos mis objetos, pasándoles lo que necesitan, en este caso App necesita el valor de __FILE__ así que se lo paso. Nota que esto crea los objetos en memoria, pero no realiza ningún trabajo. La aplicación del plugin está lista para funcionar, para entrar en acción, pero está en la fase de preparación.

El siguiente paso podría ser pasar estos objetos a un conjunto de pruebas unitarias, pero ahora voy a ejecutarlos. He terminado mi proceso de inicio, y la aplicación está lista para ejecutarse, así que disparo el método run. No debería necesitar pasar nada a run ya que todo lo necesario se ha pasado a los constructores. Durante el método run, agrego todos mis filtros y hooks. Es durante esos hooks que se ejecutan todas las demás partes del plugin.

La parte importante sin embargo es que tengo una estructura definida, y que paso lo necesario durante la fase de construcción/inicio, con un ciclo de vida definido.

10 ago 2017 17:02:24
Comentarios

Gracias por la respuesta detallada. Por lo que entiendo de tu explicación, al menos lo que estoy comprendiendo, es que necesito encontrar alguna forma para que mi plugin principal tenga conocimiento de todas las clases que he escrito? Por ejemplo, probablemente tendré archivos de clases en una carpeta "inc", y más archivos de clases en una carpeta "admin". Tenía la impresión de que cada vez que quisiera usar una clase en algún lugar, necesitaba hacer "$var = new NombreDeClase();" para poder acceder a esos métodos. Por eso me ves creando la clase dentro de la clase principal.

Dan Zuzevich Dan Zuzevich
10 ago 2017 17:17:21

no tienes que crear el objeto dentro de la clase, simplemente pasa $var a ella, los constructores también pueden recibir parámetros y los objetos se pueden pasar como cualquier otra variable. Puedes hacerlo como lo has hecho, pero hay desventajas. El problema con acceder a __FILE__ es uno de ellos, ese valor debe venir del archivo principal del plugin, o no será correcto, así que necesita ser pasado a otras partes del plugin

Tom J Nowell Tom J Nowell
10 ago 2017 17:25:59

Perfecto. Gracias. Esta respuesta definitivamente será suficiente. Ahora sé en qué necesito repasar.

Dan Zuzevich Dan Zuzevich
10 ago 2017 17:28:15

El aspecto de programación orientada a objetos (OO) de esta pregunta está un poco fuera de tema para este sitio, mantuve la pregunta abierta con el pretexto de usar correctamente el register_activation_hook. Para el lado OO de las cosas, mira https://tomjn.com/2015/06/24/root-composition-in-wordpress-plugins/ pero hay otras formas de hacerlo. No hay una forma correcta de hacerlo, todas tienen sus beneficios y desventajas, podrías escribir tu plugin usando puramente construcciones de programación funcional al estilo Haskell

Tom J Nowell Tom J Nowell
10 ago 2017 17:28:41

¿Debería reformular la pregunta de alguna manera o está bien así?

Dan Zuzevich Dan Zuzevich
10 ago 2017 17:29:19

las ediciones para clarificar siempre son bienvenidas, solo no cambies el significado de la pregunta

Tom J Nowell Tom J Nowell
10 ago 2017 17:30:00
Mostrar los 1 comentarios restantes
0

Puedes definir una constante que contendrá el valor de __FILE__ y usarla donde quieras en tus clases.

Ejemplo:

archivo-principal-del-plugin.php

<?php
/**
* Plugin Name: Plugin con Clases
*/

define( 'PLUGIN_WITH_CLASSES__FILE__', __FILE__ );

include( 'mi-clase.php' );

mi-clase.php

<?php
class myClass {
  function __construct() {
    // Ejecutar esto al activar el plugin
    register_activation_hook( PLUGIN_WITH_CLASSES__FILE__,  [ $this, 'crear_tabla_bd_del_plugin' ] );
  }

  function crear_tabla_bd_del_plugin(){
    //Crear tabla en la base de datos...
  }
}

new myClass();
12 sept 2019 04:33:38
2

Realmente deberías registrar tus hooks de activación/desactivación/desinstalación fuera de tu clase de plugin, como se explica en la respuesta de kaiser aquí, que proporciona una explicación mucho más completa sobre el tema de lo que yo podría escribir.

Eso debería cubrirte si estás buscando escribir este plugin como un ejercicio de aprendizaje. Si lo único que buscas es un plugin de SEO que funcione bien y no esté plagado de anuncios horteras como Yoast, puedo recomendarte encarecidamente The SEO Framework.

10 ago 2017 16:52:31
Comentarios

Esto fue útil y agradezco la respuesta.

Dan Zuzevich Dan Zuzevich
10 ago 2017 17:19:23

He votado a favor de la respuesta ya que es definitivamente un recurso útil.

Dan Zuzevich Dan Zuzevich
11 ago 2017 16:22:44
1

Tom McFarlin creó un boilerplate para plugins muy completo (ahora mantenido por Devin Vinson), todo escrito en POO (Programación Orientada a Objetos), que puedes usar tanto para crear tu nuevo plugin como para estudiar el flujo de la aplicación y responder a tu pregunta. Lo he utilizado en varios plugins personalizados y debo decir que realmente me abrió los ojos a algunos de los misterios de la POO.

10 ago 2017 17:38:29
Comentarios

Esta es una muy buena idea. Me había olvidado por completo del plugin boilerplate. Lo había revisado hace un tiempo y me resultó bastante confuso. Pero definitivamente voy a descargarlo nuevamente y usarlo como referencia. Muchas gracias por la sugerencia.

Dan Zuzevich Dan Zuzevich
11 ago 2017 16:20:47