add_action referencia a una clase

38

¿Es posible hacer referencia a una clase en lugar de una función en 'add_action'? Parece que no puedo entenderlo. Aquí hay un ejemplo básico de la función en cuestión.

add_action( 'admin_init', 'MyClass' );
class MyClass {
     function __construct() {
          .. This is where stuff gets done ..
     }
}

Entonces sí, eso no funciona. También he intentado:

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

Y:

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

Y también:

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

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

Matthew Ruddy
fuente

Respuestas:

69

No, no puede 'inicializar' o instanciar la clase a través de un gancho, no directamente. Siempre se requiere algún código adicional (y no es deseable poder hacerlo, ya que está abriendo una lata de gusanos para usted).

Aquí hay una mejor manera de hacerlo:

class MyClass {
     function __construct() {
          add_action( 'admin_init',array( $this, 'getStuffDone' ) );
     }
     function getStuffDone() {
          // .. This is where stuff gets done ..
     }
}
$var = new MyClass();

Por supuesto, uno podría crear una clase de interfaz para simplificarla aún más para el caso general:

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

Tenga en cuenta que, como interfaz, no puede crear un objeto de este tipo directamente, pero puede crear una subclase, que le permite decir:

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

Lo que luego agregaría automáticamente todos los ganchos, ¡solo necesita definir qué se está haciendo exactamente en una subclase y luego crearlo!

En constructores

No agregaría un constructor como una función de enlace, es una mala práctica y puede provocar muchos eventos inusuales. Además, en la mayoría de los idiomas, un constructor devuelve el objeto que se está instanciando, por lo que si su gancho necesita devolver algo como en un filtro, no devolverá la variable filtrada que desee, sino que devolverá el objeto de clase.

Llamar a un constructor o un destructor es una muy, muy, muy mala práctica de programación, sin importar en qué idioma se encuentre, y nunca se debe hacer.

Los constructores también deben construir objetos, para inicializarlos listos para usar, no para el trabajo real. El trabajo a realizar por el objeto debe estar en una función separada.

Métodos de clase estática y sin necesidad de instanciar / inicializar en absoluto

Si su método de clase es un método de clase estático, puede pasar el nombre de la clase entre comillas en lugar de $thiscomo se muestra a continuación:

class MyClass {
     public static function getStuffDone() {
          // .. This is where stuff gets done ..
     }
}
add_action( 'admin_init', array('MyClass','getStuffDone' ) );

Closures y PHP 5.3

Lamentablemente, no puede evitar la línea que crea la nueva clase. La única otra solución para omitirlo sería el código de placa de caldera que todavía tiene esa línea, y requeriría PHP 5.3+, por ejemplo:

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

En ese momento, también puede saltear la clase y simplemente usar una función:

add_action('admin_init',function(){
    // do stuff
});

Pero tenga en cuenta que ahora ha introducido el espectro de funciones anónimas. No hay forma de eliminar la acción anterior utilizando remove_action, y esto puede causar un gran dolor a los desarrolladores que tienen que trabajar con el código de otras personas.

En los símbolos

Puede ver acciones utilizadas de esta manera:

array( &$this, 'getStuffDone' );

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

No hay ninguna razón para usar &thisal agregar ganchos y filtros, y eliminar la referencia no causará problemas e incluso puede mejorar la compatibilidad con futuras versiones de PHP

Use esto en su lugar:

array( $this, 'getStuffDone' );
Tom J Nowell
fuente
Okay. Gracias por todo eso; Realmente aprendí bastante. Realmente solo me estoy sintiendo cómodo en PHP basado en clases ahora. Esto es lo que he hecho, y funciona, pero ¿podría decirme si es una mala práctica / incorrecta de alguna manera? He iniciado la clase dentro de una función estática, dentro de la clase misma. Luego hizo referencia a la función estática en add_action. Ver este enlace: pastebin.com/0idyPwwY
Matthew Ruddy
sí, podría hacerlo de esa manera, aunque podría evitar usarlo $classcomo su nombre de variable, esas palabras tienden a estar reservadas. Creo que está haciendo todo lo posible para evitar decir algo similar al $x = new Y();alcance global, y está agregando complejidad donde no es necesario. ¡Su intento de reducir la cantidad de código escrito ha implicado escribir más código!
Tom J Nowell
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 tiene el mismo propósito. Es un desperdicio de recursos. Recuerde, hay un costo para crear y destruir un objeto, quiere pocos objetos y desea que sean de larga duración
Tom J Nowell
Buen punto. Cambió la forma en que lo he estado mirando. Creo que lo llamaré en el ámbito global, evitando el código adicional.
Matthew Ruddy
1
Me gustaría agregar que si ha puesto su clase en un espacio de nombres, tendrá que agregar eso también o add_action () / add_filter () no lo encontrará, así:add_action( 'admin_init', array('MyNamespace\MyClass','getStuffDone' ) );
jschrab
10

Clase de ejemplo

Notas:

  • Inicia la clase solo una vez
    • Llame a la prioridad 0, para que pueda usar el mismo enlace con la prioridad predeterminada más adelante
    • Envuélvala en una ! class_existspara evitar llamarla dos veces y coloque la llamada de inicio dentro
  • Hacer la initfunción y la clase varstatic
  • Llame al constructor desde dentro de su init, cuando llame a la clase new self.

Aquí hay un ejemplo

if ( ! class_exists( 'WPSESampleClass' ) )
{
    // Init the class on priority 0 to avoid adding priority inside the class as default = 10
    add_action( 'init', array ( 'WPSESampleClass', 'init' ), 0 );

class WPSESampleClass
{
    /**
     * The Class Object
     */
    static private $class = null;

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

        return self :: $class;
    }

    public function __construct()
    {
        // do stuff like add action calls:
        add_action( 'init', array( $this, 'cb_fn_name' ) );
    }

    public function cb_fn_name()
    {
        // do stuff 
    }
} // END Class WPSESampleClass

} // endif;

Php 5+

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

emperador
fuente
2
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('All In One Woo', 'all_in_one_woo'); ?></h1>
            </div>
        <?php }
    }// end class
}// end if

if (class_exists("AllInOneWoo")){       
    $all_in_one_woo = new AllInOneWoo();
}
Zakir Sajib
fuente
Por favor, editar su respuesta , y añadir una explicación: ¿por qué podría que resolver el problema?
fuxia
0

Puede activar eventos en su clase sin la necesidad de cargarlo inicialmente . Esto es útil si no desea cargar toda la clase por adelantado, pero necesita 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 . 'Working without loading the object';
    }
}

MyClass::init();

Esta es una versión muy simplificada de la respuesta de Kaiser, pero muestra en términos simples el comportamiento. Puede ser útil para aquellos que buscan 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 complemento pongan en cola los scripts, pero solo desencadenar el objeto en una solicitud AJAX.

John Reid
fuente
0

Esto funciona para mi:

class foo
{
    public static function bar()
    {
        echo 'it works!';
    }
}

add_action('foo_bar', array('foo', 'bar'));
Jonathan
fuente
0

En términos generales, no agregaría una clase completa a un gancho. Los add_action()/ add_filter()hooks esperan funciones de devolución de llamada , a las que se puede hacer referencia desde dentro de una clase .

Digamos que tiene una init()función dentro de su clase, que desea conectar al gancho de WordPress init.

Ponga su add_action()llamada dentro de su clase, y luego identifique la devolución de llamada de esta manera:

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

(Nota: supongo que su clase tiene un espacio de nombres adecuado; de lo contrario, asegúrese de asignar un espacio de nombres a sus funciones de devolución de llamada).

Chip Bennett
fuente
¿Qué pasa si la clase actual aún no se ha iniciado? Estaba tratando de usar add_action para iniciar realmente, así que no tengo que agregar $ var = new MyClass (); de antemano en otro lugar.
Matthew Ruddy
¿No puede simplemente escribir una devolución de llamada para crear una instancia de su clase en init(o donde lo necesite)?
Chip Bennett
0

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

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

(En teoría, su 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 lo hace. ¿Tal vez si no llama por referencia?)

Gargantas de Boone
fuente
El primero no funciona. Solo hace que la página quede en blanco. El segundo funciona, pero solo porque la clase se ha iniciado en la primera línea. De alguna manera, derrota el propósito de agregar la acción, porque la clase ya se ha iniciado. Pero no hace lo que estoy tratando de hacer, 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 se inicie la función 'MyClass' si la acción en la otra clase no está allí. Perdón si me falta algo; aprendiendo sobre la marcha
Matthew Ruddy
Sí, estaba equivocado Eso solo funciona si estás llamando a un initmétodo estático . Ver wordpress.stackexchange.com/a/48093/14052
Gargantas de Boone