programación orientada a objetos en WordPress

Programación orientada a objetos sobre WordPress

Sin ánimo de ofender a nadie, es innegable que WordPress tiene mala fama en algunos aspectos, y uno de ellos es el cajón de sastre que puede llegar a ser un functions.php o un index.php de un plugin. También es cierto que esa tendencia está cambiando, que se ven cosas más trabajadas, pero se siguen viendo otras que pa’qué

Yo mismo «recomiendo» en muchos de mi posts algo tipo: pega esto en tu functions.php o en tu plugin de utilidades, pero al fin y al cabo esos ejemplos no son más que eso, ejemplos con fines didácticos sobre cómo desarrollar una funcionalidad. No merece la pena extender un post, explicar cómo crearte una estructura de carpetas, crear un composer para utilizar namespaces, cómo crear una clase, cómo instanciarla, etc… para por ejemplo algo tan tonto como cambiar la longitud del extracto.

Últimamente he escrito algunos posts algo más avanzados tipo principios SOLID, filosofías para desarrolladores, namespaces, named constructors, strict types, guardas, tests… pero me faltaba uno: «cómo trabajar con programación orientada a objetos en WordPress», o al menos una propuesta sobre cómo hacerlo, ya que como verás la verdad absoluta no existe, y al final dependerá de cómo quieras hacerlo tú.

El código espaguetti

Imagino que esto del código espaguetti lo habrás oído en alguna ocasión, la mejor definición que he encontrado está en la Wikipedia:

El código espagueti es un término peyorativo para los programas de computación que tienen una estructura de control de flujo compleja e incomprensible. Su nombre deriva del hecho que este tipo de código parece asemejarse a un plato de espaguetis, es decir, un montón de hilos intrincados y anudados.

Wikipedia

Imagino también que si eres desarrollador, te habrá venido a la cabeza el típico functions.php de 6.000 líneas, donde funciones llaman a otras funciones situadas 2.500 líneas más abajo, sin control, sin orden, sin estructura, con copipastes, funciones con un prefijo, otras con otro, otras sin él…

Sólo a modo de ejemplo, así luce mi functions.php:

<?php

require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/src/Bootstrap.php';

Bootstrap::instance()->run();

Instancio mi clase Bootstrap y la inicio, simplemente. Esta clase se encarga de poner en marcha la rueda e ir lanzando otras clases… estructuro el código por «módulos», organizo todo en carpetas, etc…

Y aquí ya viene la primera traba: cómo conjugar tu estructura con WordPress y sus hooks sin morir en el intento.

Hooks de WordPress con programación orientada a objetos

En un primer momento puedes pensar:

  • Creo una clase, la instancio y en el __construct() la enlazo con el hook de WordPress correspondiente.
  • Pongo los hooks en un método init(), load(), run() o similar e instancio la clase y después ejecuto el método correspondiente.
  • Pongo los hooks fuera de la clase y creo los métodos estáticos y los llamo desde el propio hook…

En realidad cualquiera de estas soluciones funcionan. Pero todas tienen sus pros y sus contras. Al final la decisión es tuya, pero vamos a verlo mejor con ejemplos. Con uno muy básico: queremos hacer «algo» en el init.

Hooks en el __construct()

<?php

namespace Your\Namespace;

class YourAwesomeClass {
    public function __construct() {
        add_action( 'init', array($this, 'do_something') );
    }
 
    public function do_something() {
        // your code here
    }
}
$yourObject = new YourAwesomeClass();

Como decía antes, esta aproximación funcionar funciona, pero tiene 2 problemas:

  • Estamos utilizando el constructor para algo que no es
  • Si quisieras cubrir tu clase con tests unitarios, no podrías

El constructor de una clase está para realizar cualquier inicialización (propiedades) que el objeto pueda necesitar antes de ser usado. Si aquí, realizas cualquier otra acción como llamar a un hook, estarás acoplando WordPress a tu constructor, impidiéndote realizar tests unitarios, ya que para realizar un test necesitarías cargar WordPress al completo, esto no tiene sentido y es un claro indicador de que algo está mal en el diseño de tu desarrollo.

Pero sí, funcionar funciona…

Hooks en un método init(), load() o run()

Esta aproximación es mejor respecto a la primera. Cuando instanciemos una clase, no estaremos utilizando el construct para algo que no es su responsabilidad. En su lugar, crearemos un método que normalmente se suele llamar init(), load() o run(), aunque puedes darle el nombre que quieras (naming things).

<?php

namespace Your\Namespace;

class YourAwesomeClass {
    public function __construct() {
        // Init properties (in case you need it)
    }

    public function init() {
        add_action( 'init', array($this, 'do_something') );
    }
 
    public function do_something() {
        // your code here
    }
}
$yourObject = new YourAwesomeClass();
$yourObject->init();

Hemos desacoplado WordPress de nuestra clase (al sacar la llamada del hook del constructor), pero si queremos realizar un test unitario sobre nuestro método init(), seguiríamos necesitando cargar WordPress para testear el método.

Recordar que esto es sólo un ejemplo, y no tendría mucho sentido testear un método que hace un add_action, es decir, quizá este método no tiene sentido darle cobertura de tests ya que es como si estuviéramos testeando el propio WordPress. Pero sí podríamos testear el método do_something().

Hooks en métodos estáticos

Otra opción es no poner los hooks dentro de la clase, e invocar a un método estático de la misma:

<?php

namespace Your\Namespace;

class YourAwesomeClass {
    public static function do_something() {
        // your code here
    }
}
add_action( 'init', array(YourAwesomeClass::class, 'do_something') );

Con esta aproximación, surgen dos «problemas»:

  • El beneficio de utilizar programación orientada a objetos sería meramente organizacional, únicamente para tener un cierto orden y estructura en tu desarrollo.
  • ¿Dónde haces esta llamada al hook? En el archivo de la clase no procede… ¿En el functions? ¿En otro php?

Loader de Hooks

Otra opción interesante es utilizar un loader de hooks. Existe un plugin boilerplate (de Devin Vinson) que incluye un ejemplo completo de un loader de hooks.

Básicamente es una clase Loader que actúa como una especie de funnel. En todas las clases de tu desarrollo, cuando tengas que hookear un método, lo haces a través del loader, que actuará como una pila, irá acumulando todos los hooks y filters que utilices o necesites en tu desarrollo, y al final los ejecutas.

Para esto, lo adecuado es que pases al constructor de la clase una instancia del Loader como inyección de dependencias (en lugar de instanciarla directamente en cada clase)

<?php

class YourAwesomeClass {
    private $loader;

    public function __construct(Loader $loader) {
        $this->loader = $loader;
    }

    public function init() {
        $this->load_hooks();
    }

    public function load_hooks() {
	$this->loader->add_action( 'init', array($this, 'do_something' );
    }

    public function do_something() {
        // your code here
    }
}

$loader = new Loader();
$yourObject = new YourAwesomeClass($loader);
$yourObject->init();

Esta aproximación puede parecer más enrevesada, pero te permite estructurar muy bien tu código. Te abre la puerta a segmentar tu código: podrías tener un load_actions, un load_filters… incluso un load_public_actions, load_admin_actions… para que a través de condicionales puedas optimizar mejor el rendimiento de tu desarrollo…

Conclusiones

Como decía en el post, al final va a depender de tus necesidades, de tus principios, de tus gustos, y sobre todo de cómo quieras organizarte. La elección es tuya. Todas tienen sus pros y sus contras.

Mi preferencia personal es por utilizar un loader, pero tampoco veo mal la opción de ponerlos en un método init(). Luego incluso podrías subdividir en otros métodos para separar los actions de los filters, o hacer alguna carga condicional.

Te animo a usar los comentarios si utilizas alguna aproximación diferente y quieres compartirla.

¿Te ha resultado útil esta información? 🍺

Si este post te ha resuelto un problema, invítame a un café o a una cerveza. Con este pequeño gesto me animas a seguir escribiendo.

Comentarios

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *:

  • El fin del tratamiento es únicamente la moderación de comentarios para evitar spam
  • La legitimación es tu consentimiento al comentar
  • No se comunicará ningún dato a terceros salvo por obligación legal
  • Tienes derecho al acceso, rectificación y eliminación de los comentarios