Referenciar una clase en add_action

5 abr 2012, 16:44:22
Vistas: 92K
Votos: 58

¿Es posible referenciar una clase en lugar de una función en 'add_action'? No logro hacerlo funcionar. Aquí un ejemplo básico de lo que intento hacer:

add_action( 'admin_init', 'MyClass' );
class MyClass {
     function __construct() {
          .. Aquí es donde se ejecuta el código ..
     }
}

Eso no funciona. También he probado:

$var = new MyClass();
add_action( 'admin_init', array( &$var ) );

Y también:

$var = new MyClass();
add_action( 'admin_init', array( &$var, '__construct' ) );

Así como:

add_action( 'admin_init', 'MyClass::__construct' );

¿Hay alguna forma de hacer esto sin tener que crear una función separada que cargue la clase? Me gustaría poder ejecutar directamente el constructor de la clase a través de add_action. Eso es todo lo que necesito para que todo funcione.

0
Todas las respuestas a la pregunta 9
6
97

No, no es posible 'inicializar' o instanciar la clase a través de un hook, no directamente.

Siempre se requiere código adicional (y no es deseable poder hacerlo, ya que te estarías metiendo en problemas).

Aquí hay mejores formas de hacerlo:

class MyClass {
     function __construct() {
          add_action( 'admin_init', [ $this, 'getStuffDone' ] );
     }
     function getStuffDone() {
          // .. Aquí es donde se hacen las cosas ..
     }
}
$var = new MyClass();

Por supuesto, se podría crear una clase interfaz para simplificarlo aún más en el caso general:

class IGetStuffDone {
    function IGetStuffDone(){
        add_action( 'admin_init', [ $this, 'getStuffDone' ] );
    }
    public abstract function getStuffDone();
}

Ten en cuenta que, como interfaz, no puedes crear un objeto de este tipo directamente, pero podrías crear una subclase, permitiéndote hacer:

class CDoingThings extends IGetStuffDone {
    function getStuffDone(){
        // hacer cosas
    }
}
$var = new CDoingThings();

Lo cual automáticamente agregaría todos los hooks, solo necesitas definir qué se hace exactamente en una subclase y luego crearla.

Sobre los Constructores

No agregaría un constructor como una función de hook, es mala práctica y puede llevar a muchos eventos inusuales. Además, en la mayoría de los lenguajes, un constructor retorna el objeto que se está instanciando, por lo que si tu hook necesita retornar algo, como en un filtro, no retornará la variable filtrada como deseas, sino que retornará el objeto de la clase.

Llamar directamente a un constructor o destructor es una muy, muy, muy mala práctica de programación, sin importar en qué lenguaje estés, y nunca debería hacerse.

Los constructores también deberían construir objetos, para inicializarlos y dejarlos listos para su uso, no para realizar el trabajo real. El trabajo que debe hacer el objeto debería estar en una función separada.

Métodos estáticos de clase y no necesitar instanciar/inicializar en absoluto

Si el método de tu clase es estático, puedes pasar el nombre de la clase entre comillas en lugar de $this como se muestra a continuación:

class MyClass {
    public static function getStuffDone() {
        // .. Aquí es donde se hacen las cosas ..
    }
}
add_action( 'admin_init', [ __NAMESPACE__ . '\MyClass','getStuffDone' ] );

Observa el uso de __NAMESPACE__, que es necesario si tu clase está dentro de un namespace.

Clausuras

Lamentablemente, no puedes evitar la línea que crea la nueva clase. La única otra solución para omitirla implicaría código repetitivo que aún tiene esa línea, por ejemplo:

add_action( 'admin_init', function() {
    $var = new MyClass();
    $var->getStuffDone();
} );

En ese punto, podrías omitir la clase y simplemente usar una función:

add_action( 'admin_init', function() {
   // hacer cosas
} );

Pero ten en cuenta que ahora has introducido el espectro de las funciones anónimas. No hay forma de eliminar la acción anterior usando remove_action, y esto puede causar grandes dolores de cabeza a los desarrolladores que tienen que trabajar con el código de otras personas.

Sobre los Ampersands

Puedes ver acciones usadas así:

array( &$this, 'getStuffDone' )

Esto es malo. & se agregó en PHP 4 cuando los objetos se pasaban como valores, no como referencias. PHP 4 tiene más de una década y WordPress no lo ha soportado en mucho tiempo.

No hay ninguna razón para usar &this al agregar hooks y filters, y eliminar la referencia no causará problemas, e incluso podría mejorar la compatibilidad con futuras versiones de PHP.

Usa esto en su lugar:

[ $this, 'getStuffDone' ]
5 abr 2012 17:16:22
Comentarios

Ok. Muchas gracias por todo eso; realmente he aprendido bastante. Recién ahora me estoy sintiendo cómodo con PHP orientado a objetos. Esto es lo que he hecho, y funciona, pero ¿podrías decirme si es una mala práctica/incorrecto de alguna manera? He inicializado la clase dentro de una función estática, dentro de la propia clase. Luego he referenciado la función estática en el add_action. Mira este enlace: http://pastebin.com/0idyPwwY

Matthew Ruddy Matthew Ruddy
5 abr 2012 17:35:30

sí, podrías hacerlo de esa manera, aunque yo evitaría usar $class como nombre de variable, esas palabras tienden a estar reservadas. Creo que estás yendo demasiado lejos para evitar decir algo similar a $x = new Y(); en el ámbito global, y estás añadiendo complejidad donde no es necesaria. ¡Tu intento de reducir la cantidad de código escrito ha involucrado escribir más código!

Tom J Nowell Tom J Nowell
5 abr 2012 17:45:59

Señalaría que en todos los casos anteriores, sería mejor usar una función en lugar de una clase, ya que esa clase se descartará de todos modos y cumple el mismo propósito. Es un desperdicio de recursos. Recuerda, hay un costo al crear y destruir un objeto, quieres pocos objetos, y quieres que sean de larga duración

Tom J Nowell Tom J Nowell
5 abr 2012 17:47:28

Buen punto. Cambié la forma en que lo estaba viendo. Creo que lo llamaré en el ámbito global en su lugar, evitando el código adicional.

Matthew Ruddy Matthew Ruddy
5 abr 2012 18:09:41

Me gustaría agregar que si has colocado tu clase en un namespace, tendrás que agregarlo también o add_action()/add_filter() no lo encontrará - así: add_action( 'admin_init', array('MyNamespace\MyClass','getStuffDone' ) );

jschrab jschrab
28 abr 2018 01:01:39

¿Qué pasaría si add_action() estuviera dentro de construct() en la misma clase?

MMTDesigner MMTDesigner
19 jun 2021 15:45:20
Mostrar los 1 comentarios restantes
0
12

Ejemplo de clase

Notas:

  • Inicializa la clase solo una vez
    • Llámala con prioridad 0, para que puedas usar el mismo hook con la prioridad por defecto más tarde
    • Envuélvela en un ! class_exists para evitar llamarla dos veces y coloca el llamador de init dentro
  • Haz la función init y la variable de clase static
  • Llama al constructor desde dentro de tu init, cuando llames a la clase new self.

Aquí tienes un ejemplo

if ( ! class_exists( 'WPSESampleClass' ) )
{
    // Inicializa la clase con prioridad 0 para evitar añadir prioridad dentro de la clase ya que por defecto es 10
    add_action( 'init', array ( 'WPSESampleClass', 'init' ), 0 );

class WPSESampleClass
{
    /**
     * El Objeto de la Clase
     */
    static private $class = null;

    public static function init()
    {
        if ( null === self::$class ) 
            self :: $class = new self;

        return self :: $class;
    }

    public function __construct()
    {
        // haz cosas como añadir llamadas a acciones:
        add_action( 'init', array( $this, 'cb_fn_name' ) );
    }

    public function cb_fn_name()
    {
        // haz cosas 
    }
} // FIN Clase WPSESampleClass

} // endif;

Php 5+

Por favor, deja el & fuera. Ya estamos más allá de php4. :)

5 abr 2012 17:16:16
0

Puedes activar eventos en tu clase sin necesidad de cargarla inicialmente. Esto es útil si no deseas cargar la clase completa desde el principio, pero necesitas acceder a los filtros y acciones de WordPress.

Aquí hay un ejemplo muy simple:

<?php
class MyClass
{
    public static function init()
    {
        add_filter( 'the_content', array('MyClass', 'myMethod') );
    }

    public static function myMethod($content)
    {
        $content = $content . 'Funcionando sin cargar el objeto';
    }
}

MyClass::init();

Esta es una versión muy simplificada de la respuesta de Kaiser, pero muestra de manera simple el comportamiento. Puede ser útil para aquellos que ven este estilo por primera vez.

Otros métodos aún pueden iniciar el objeto si es necesario. Personalmente estoy usando este método para permitir que partes opcionales de mi plugin carguen scripts, pero solo activando el objeto en una solicitud AJAX.

22 dic 2014 16:02:08
1
if (!class_exists("AllInOneWoo")){
    class AllInOneWoo {
        function __construct(){
            add_action('admin_menu', array($this, 'all_in_one_woo') );
        }
        function all_in_one_woo(){
            $page_title = 'All In One Woo';
            $menu_title = 'All In One Woo';
            $capability = 'manage_options';
            $menu_slug  = 'all-in-one-woo-menu';
            $function   = array($this, 'all_in_one_woo_menu');
            $icon_url   = 'dashicons-media-code';
            $position   = 59;

            add_menu_page($page_title, $menu_title, $capability, $menu_slug, $function, $icon_url, $position);
        }
        function all_in_one_woo_menu(){?>
            <div class="wrap">
                <h1><?php _e('Todo en Uno Woo', 'all_in_one_woo'); ?></h1>
            </div>
        <?php }
    }// fin de la clase
}// fin del if

if (class_exists("AllInOneWoo")){       
    $all_in_one_woo = new AllInOneWoo();
}
4 mar 2018 10:14:23
Comentarios

Por favor, [edita] tu respuesta y agrega una explicación: ¿por qué esto podría resolver el problema?

fuxia fuxia
4 mar 2018 16:06:06
0

Esto funciona para mí:

class foo
{
    public static function bar()
    {
        echo '¡funciona!';
    }
}

add_action('foo_bar', array('foo', 'bar'));
17 mar 2015 20:25:38
1

Respuesta 2025

Definitivamente puedes hacer esto ahora con el __invoke método mágico. Este se llama cuando intentas invocar la clase como una función.

class MyHandler {
  public function __construct(){ /* ... */ }

  public function __invoke(){
    // cualquier acción que quieras realizar aquí
  }
}

add_action('admin_init', new MyHandler);
5 mar 2025 22:22:13
Comentarios

Aunque creo que esta es una respuesta interesante, uno debería considerar si realmente es la mejor opción para su caso de uso particular. Personalmente, no puedo pensar en un caso de uso donde esto sería más legible o "mejor" que cualquiera de las alternativas en este tema.

Howdy_McGee Howdy_McGee
5 mar 2025 22:31:07
2

En términos generales, no agregarías una clase completa a un hook. Los hooks add_action()/add_filter() esperan funciones de callback, que pueden ser referenciadas desde dentro de una clase.

Digamos que tienes una función init() dentro de tu clase, que deseas enganchar al hook init de WordPress.

Coloca tu llamada a add_action() dentro de tu clase, e identifica el callback de la siguiente manera:

add_action( 'init', array( $this, 'init' ) );

(Nota: Asumo que tu clase está correctamente namespaced; de lo contrario, asegúrate de namespacear tus funciones de callback).

5 abr 2012 17:03:00
Comentarios

¿Qué pasa si la clase actual no ha sido iniciada todavía? Estaba intentando usar add_action para iniciarla realmente, así no tengo que añadir $var = new MyClass(); previamente en otro lugar.

Matthew Ruddy Matthew Ruddy
5 abr 2012 17:08:41

¿No puedes simplemente escribir un callback para instanciar tu clase en init (o donde la necesites)?

Chip Bennett Chip Bennett
5 abr 2012 18:55:28
2

Deberías poder hacerlo pasando el nombre de la clase en lugar del objeto instanciado:

add_action( 'init', array( 'MyClass', '__construct' ) );

(En teoría, tu otra solución también debería funcionar

$var = new MyClass();
add_action( 'admin_init', array( $var, '__construct' ) );

No estoy seguro de por qué no funciona. ¿Quizás si no llamas por referencia?)

5 abr 2012 17:09:54
Comentarios

El primero no funciona. Simplemente deja la página en blanco. El segundo funciona, pero solo porque la clase ya ha sido iniciada en la primera línea. En cierto modo, pierde el propósito de añadir la acción, porque la clase ya se ha iniciado. Pero no hace lo que intento lograr, que es iniciar la clase a través de la acción. En el código real en el que estoy trabajando, la acción no es 'admin_init' sino una acción personalizada dentro de otra clase. No quiero que la función 'MyClass' se inicie si la acción en la otra clase no está presente. Perdón si me estoy perdiendo algo; estoy aprendiendo sobre la marcha.

Matthew Ruddy Matthew Ruddy
5 abr 2012 17:15:26

Sí, me equivoqué. Eso solo funciona si estás llamando a un método init estático. Mira http://wordpress.stackexchange.com/a/48093/14052

Boone Gorges Boone Gorges
5 abr 2012 18:17:59
0

Ejemplo de método estático

class FrmTransLiteEntriesController {

    /**
     * Incluir detalles de pago en la barra lateral de la entrada.
     *
     * @param stdClass $entry
     * @return void
     */
    public static function sidebar_list( $entry ) {
    

        add_action( 'frm_entry_shared_sidebar_middle', __CLASS__ . '::show_sidebar_list' );
    }

    
    public static function show_sidebar_list( $entry ) {
    }
}
16 sept 2023 10:25:33