¿Cuál es la mejor manera de iniciar una clase en un plugin de WordPress?
He creado un plugin y, como es natural en mí, quise utilizar un buen enfoque orientado a objetos. Lo que he estado haciendo es crear esta clase y justo debajo crear una instancia de la misma:
class ClassName {
public function __construct(){
}
}
$class_instance = new ClassName();
Asumo que debe haber una forma más "WordPress" de iniciar esta clase, y entonces me encontré con personas que dicen que prefieren tener una función init()
en lugar de una __construct()
. Y similarmente encontré algunas personas usando el siguiente hook:
class ClassName {
public function init(){
}
}
add_action( 'load-plugins.php', array( 'ClassName', 'init' ) );
¿Cuál es generalmente considerada la mejor manera de crear una instancia de clase de WP en la carga y tenerla como una variable accesible globalmente?
NOTA: Como punto interesante adicional, he notado que mientras register_activation_hook()
puede ser llamado desde dentro del __construct
, no puede ser llamado desde dentro del init()
usando el segundo ejemplo. Quizás alguien podría explicarme este punto.
Edición: Gracias por todas las respuestas, claramente hay bastante debate sobre cómo manejar la inicialización dentro de la clase misma, pero creo que hay un consenso bastante bueno de que add_action( 'plugins_loaded', ...);
es la mejor manera de iniciarlo...
Edición: Solo para confundir las cosas, también he visto que se usa esto (aunque yo no usaría este método porque convertir una clase bien orientada a objetos en una función parece derrotar el propósito de la misma):
// Iniciar este plugin
add_action( 'init', 'ClassName' );
function ClassName() {
global $class_name;
$class_name = new ClassName();
}

Llegando aquí exactamente 2 años después de que se hiciera la pregunta original, hay algunas cosas que quiero señalar. (No me pidas que señale muchas cosas, nunca).
Hook adecuado
Para instanciar una clase de plugin, se debe usar el hook adecuado. No hay una regla general sobre cuál es, porque depende de lo que haga la clase.
Usar un hook muy temprano como "plugins_loaded"
a menudo no tiene sentido porque un hook como ese se dispara para solicitudes de admin, frontend y AJAX, pero muy frecuentemente un hook posterior es mucho mejor porque permite instanciar clases de plugin solo cuando se necesitan.
Ejemplo: una clase que hace cosas para plantillas puede instanciarse en "template_redirect"
.
En términos generales, es muy raro que una clase necesite ser instanciada antes de que se haya disparado "wp_loaded"
.
No a la Clase Dios
La mayoría de las clases usadas como ejemplos en respuestas antiguas usan una clase llamada algo como "Prefix_Example_Plugin"
o "My_Plugin"
... Esto indica que probablemente hay una clase principal para el plugin.
Bueno, a menos que un plugin esté hecho por una sola clase (en cuyo caso nombrarla como el plugin es absolutamente razonable), crear una clase que gestione todo el plugin (ej. añadiendo todos los hooks que necesita un plugin o instanciando todas las demás clases del plugin) puede considerarse una mala práctica, como ejemplo de un objeto dios.
En programación orientada a objetos, el código debería tender a ser S.O.L.I.D. donde la "S" significa "Principio de responsabilidad única".
Significa que cada clase debería hacer una sola cosa. En el desarrollo de plugins para WordPress significa que los desarrolladores deberían evitar usar un solo hook para instanciar una clase principal del plugin, sino que se deberían usar diferentes hooks para instanciar diferentes clases, según la responsabilidad de la clase.
Evitar hooks en el constructor
Este argumento se ha introducido en otras respuestas aquí, sin embargo quiero remarcar este concepto y enlazar esta otra respuesta donde se ha explicado bastante ampliamente en el contexto de pruebas unitarias.
Casi 2015: PHP 5.2 es para zombis
Desde el 14 de agosto de 2014, PHP 5.3 alcanzó su fin de vida. Definitivamente está muerto. PHP 5.4 va a ser soportado durante todo 2015, lo que significa otro año en el momento en que escribo.
Sin embargo, WordPress todavía soporta PHP 5.2, pero nadie debería escribir una sola línea de código que soporte esa versión, especialmente si el código es POO.
Hay diferentes razones:
- PHP 5.2 murió hace mucho tiempo, no se lanzan correcciones de seguridad para él, lo que significa que no es seguro
- PHP 5.3 añadió muchas características a PHP, funciones anónimas y espacios de nombres über alles
- las versiones más nuevas de PHP son mucho más rápidas. PHP es gratis. Actualizarlo es gratis. ¿Por qué usar una versión más lenta e insegura si puedes usar una más rápida y segura gratis?
Si no quieres usar código PHP 5.4+, usa al menos 5.3+
Ejemplo
En este punto es hora de revisar respuestas antiguas basándonos en lo que he dicho hasta aquí.
Una vez que no tenemos que preocuparnos por 5.2, podemos y debemos usar espacios de nombres.
Para explicar mejor el principio de responsabilidad única, mi ejemplo usará 3 clases, una que hace algo en el frontend, una en el backend y una tercera usada en ambos casos.
Clase de admin:
namespace GM\WPSE\Example;
class AdminStuff {
private $tools;
function __construct( ToolsInterface $tools ) {
$this->tools = $tools;
}
function setup() {
// configurar clase, quizá añadir hooks
}
}
Clase de frontend:
namespace GM\WPSE\Example;
class FrontStuff {
private $tools;
function __construct( ToolsInterface $tools ) {
$this->tools = $tools;
}
function setup() {
// configurar clase, quizá añadir hooks
}
}
Interfaz Tools:
namespace GM\WPSE\Example;
interface ToolsInterface {
function doSomething();
}
Y una clase Tools, usada por las otras dos:
namespace GM\WPSE\Example;
class Tools implements ToolsInterface {
function doSomething() {
return 'done';
}
}
Teniendo estas clases, puedo instanciarlas usando hooks adecuados. Algo como:
require_once plugin_dir_path( __FILE__ ) . 'src/ToolsInterface.php';
require_once plugin_dir_path( __FILE__ ) . 'src/Tools.php';
add_action( 'admin_init', function() {
require_once plugin_dir_path( __FILE__ ) . 'src/AdminStuff.php';
$tools = new GM\WPSE\Example\Tools;
global $admin_stuff; // esto no es ideal, la razón se explica abajo
$admin_stuff = new GM\WPSE\Example\AdminStuff( $tools );
} );
add_action( 'template_redirect', function() {
require_once plugin_dir_path( __FILE__ ) . 'src/FrontStuff.php';
$tools = new GM\WPSE\Example\Tools;
global $front_stuff; // esto no es ideal, la razón se explica abajo
$front_stuff = new GM\WPSE\Example\FrontStuff( $tools );
} );
Inversión de Dependencias & Inyección de Dependencias
En el ejemplo anterior usé espacios de nombres y funciones anónimas para instanciar diferentes clases en diferentes hooks, poniendo en práctica lo dicho anteriormente.
Nota cómo los espacios de nombres permiten crear clases nombradas sin ningún prefijo.
Apliqué otro concepto que se mencionó indirectamente arriba: Inyección de Dependencias, es un método para aplicar el Principio de Inversión de Dependencias, la "D" en el acrónimo SOLID.
La clase Tools
se "inyecta" en las otras dos clases cuando se instancian, así es posible separar responsabilidades.
Además, las clases AdminStuff
y FrontStuff
usan type hinting para declarar que necesitan una clase que implemente ToolsInterface
.
De esta manera nosotros o los usuarios que usen nuestro código pueden usar diferentes implementaciones de la misma interfaz, haciendo que nuestro código no esté acoplado a una clase concreta sino a una abstracción: de eso se trata exactamente el Principio de Inversión de Dependencias.
Sin embargo, el ejemplo anterior puede mejorarse aún más. Veamos cómo.
Autoloader
Una buena forma de escribir código POO más legible es no mezclar definiciones de tipos (Interfaces, Clases) con otro código, y poner cada tipo en su propio archivo.
Esta regla es también uno de los estándares de codificación PSR-11.
Sin embargo, haciendo esto, antes de poder usar una clase hay que requerir el archivo que la contiene.
Esto puede ser abrumador, pero PHP proporciona funciones de utilidad para cargar automáticamente una clase cuando se necesita, usando un callback que carga un archivo basado en su nombre.
Usando espacios de nombres se vuelve muy fácil, porque ahora es posible hacer coincidir la estructura de carpetas con la estructura de espacios de nombres.
Eso no solo es posible, sino que también es otro estándar PSR (o mejor 2: PSR-0 ahora obsoleto, y PSR-4).
Siguiendo esos estándares es posible usar diferentes herramientas que manejan autoload, sin tener que codificar un autoloader personalizado.
Tengo que decir que los estándares de codificación de WordPress tienen reglas diferentes para nombrar archivos.
Así que al escribir código para el núcleo de WordPress, los desarrolladores tienen que seguir las reglas de WP, pero al escribir código personalizado es elección del desarrollador, aunque usar estándares PSR es más fácil para usar herramientas ya escritas2.
Acceso Global, Patrón Registry y Patrón Service Locator.
Uno de los mayores problemas al instanciar clases de plugins en WordPress, es cómo acceder a ellas desde varias partes del código.
WordPress mismo usa el enfoque global: las variables se guardan en el ámbito global, haciéndolas accesibles en todas partes. Todo desarrollador de WP escribe la palabra global
miles de veces en su carrera.
Este es también el enfoque que usé para el ejemplo anterior, pero es malvado.
Esta respuesta ya es demasiado larga para permitirme explicar más por qué, pero leer los primeros resultados en el SERP para "variables globales malvadas" es un buen punto de partida.
¿Pero cómo es posible evitar variables globales?
Hay diferentes formas.
Algunas de las respuestas antiguas aquí usan el enfoque de instancia estática.
public static function instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new self;
}
return self::$instance;
}
Es fácil y bastante bien, pero obliga a implementar el patrón para cada clase a la que queramos acceder.
Además, muchas veces este enfoque lleva a caer en el problema de la clase dios, porque los desarrolladores hacen accesible una clase principal usando este método, y luego la usan para acceder a todas las demás clases.
Ya expliqué lo mala que es una clase dios, así que el enfoque de instancia estática es una buena forma de proceder cuando un plugin solo necesita hacer accesible una o dos clases.
Esto no significa que solo se pueda usar para plugins con solo un par de clases, de hecho, cuando se usa correctamente el principio de inyección de dependencias, es posible crear aplicaciones bastante complejas sin necesidad de hacer accesible globalmente un gran número de objetos.
Sin embargo, a veces los plugins necesitan hacer accesibles algunas clases, y en ese caso el enfoque de instancia estática es abrumador.
Otro enfoque posible es usar el patrón registry.
Esta es una implementación muy simple del mismo:
namespace GM\WPSE\Example;
class Registry {
private $storage = array();
function add( $id, $class ) {
$this->storage[$id] = $class;
}
function get( $id ) {
return array_key_exists( $id, $this->storage ) ? $this->storage[$id] : NULL;
}
}
Usando esta clase es posible almacenar objetos en el objeto registry mediante un id, así que teniendo acceso a un registry es posible tener acceso a todos los objetos. Por supuesto cuando un objeto se crea por primera vez hay que añadirlo al registry.
Ejemplo:
global $registry;
if ( is_null( $registry->get( 'tools' ) ) ) {
$tools = new GM\WPSE\Example\Tools;
$registry->add( 'tools', $tools );
}
if ( is_null( $registry->get( 'front' ) ) ) {
$front_stuff = new GM\WPSE\Example\FrontStuff( $registry->get( 'tools' ) );
$registry->add( 'front', front_stuff );
}
add_action( 'wp', array( $registry->get( 'front' ), 'wp' ) );
El ejemplo anterior deja claro que para ser útil el registry necesita ser accesible globalmente. Una variable global solo para el registry no es muy mala, sin embargo para los puristas no globales es posible implementar el enfoque de instancia estática para un registry, o quizá una función con una variable estática:
function gm_wpse_example_registry() {
static $registry = NULL;
if ( is_null( $registry ) ) {
$registry = new GM\WPSE\Example\Registry;
}
return $registry;
}
La primera vez que se llama a la función instanciará el registry, en llamadas posteriores simplemente lo devolverá.
Otro método específico de WordPress para hacer una clase accesible globalmente es devolver una instancia de objeto desde un filtro. Algo así:
$registry = new GM\WPSE\Example\Registry;
add_filter( 'gm_wpse_example_registry', function() use( $registry ) {
return $registry;
} );
Después de eso, donde sea que se necesite el registry:
$registry = apply_filters( 'gm_wpse_example_registry', NULL );
Otro patrón que se puede usar es el patrón service locator. Es similar al patrón registry, pero los service locators se pasan a varias clases usando inyección de dependencias.
El principal problema con este patrón es que oculta las dependencias de las clases haciendo el código más difícil de mantener y leer.
Contenedores DI
No importa el método usado para hacer accesible globalmente el registry o el service locator, los objetos tienen que almacenarse allí, y antes de almacenarse necesitan instanciarse.
En aplicaciones complejas, donde hay bastantes clases y muchas de ellas tienen varias dependencias, instanciar clases requiere mucho código, así que aumenta la posibilidad de bugs: el código que no existe no puede tener bugs.
En los últimos años han aparecido algunas bibliotecas PHP que ayudan a los desarrolladores PHP a instanciar y almacenar instancias de objetos fácilmente, resolviendo automáticamente sus dependencias.
Estas bibliotecas se conocen como Contenedores de Inyección de Dependencias porque son capaces de instanciar clases resolviendo dependencias y también de almacenar objetos y devolverlos cuando se necesitan, actuando de forma similar a un objeto registry.
Usualmente, al usar contenedores DI, los desarrolladores tienen que configurar las dependencias para cada clase de la aplicación, y luego la primera vez que una clase se necesita en el código se instancia con las dependencias adecuadas y la misma instancia se devuelve una y otra vez en solicitudes posteriores.
Algunos contenedores DI también son capaces de descubrir dependencias automáticamente sin configuración, pero usando reflexión PHP.
Algunos contenedores DI bien conocidos son:
y muchos otros.
Quiero señalar que para plugins simples, que involucran pocas clases y las clases no tienen muchas dependencias, probablemente no vale la pena usar contenedores DI: el método de instancia estática o un registry accesible globalmente son buenas soluciones, pero para plugins complejos los beneficios de un contenedor DI se vuelven evidentes.
Por supuesto, incluso los objetos contenedor DI tienen que ser accesibles para usarse en la aplicación y para ese propósito es posible usar uno de los métodos vistos arriba, variable global, variable de instancia estática, devolver objeto via filtro y así.
Composer
Usar contenedor DI a menudo significa usar código de terceros. Hoy en día, en PHP, cuando necesitamos usar una librería externa (así que no solo contenedores DI, sino cualquier código que no sea parte de la aplicación), simplemente descargarla y ponerla en nuestra carpeta de aplicación no se considera una buena práctica. Incluso si somos los autores de ese otro trozo de código.
Desacoplar el código de una aplicación de dependencias externas es señal de mejor organización, mejor fiabilidad y mejor cordura del código.
Composer, es el estándar de facto en la comunidad PHP para gestionar dependencias PHP. Lejos de ser mainstream también en la comunidad WP, es una herramienta que todo desarrollador PHP y WordPress debería al menos conocer, si no usar.
Esta respuesta ya tiene tamaño de libro para permitir más discusión, y también discutir Composer aquí probablemente está fuera de tema, solo se mencionó por completitud.
Para más información visita el sitio de Composer y también vale la pena leer este minisitio curado por @Rarst.
1 PSR son estándares de reglas PHP publicados por el PHP Framework Interop Group
2 Composer (una librería que se mencionará en esta respuesta) entre otras cosas también contiene una utilidad de autoloader.

Una nota adicional, PHP 5.3 también ha llegado al final de su vida útil. Un proveedor de hosting responsable ofrecerá al menos la versión 5.4 como opción, si no como predeterminada

"Desde el 14 de agosto de 2014, PHP 5.3 alcanzó su fin de vida. Definitivamente está muerto." Es la primera línea bajo "PHP 5.2 es para zombies" @TomJNowell

En un esfuerzo por evitar variables globales, me gusta la idea del patrón Registry con función y variable estática. La primera vez que se llama a la función, instanciará el registro; en llamadas posteriores, simplemente lo devolverá.

El mayor problema con el registro es que crea un desorden de variables y los errores tipográficos pueden causar muchos problemas. Si estás usando clases predefinidas, todos los IDE modernos te proporcionarán una visión general de las propiedades de la clase, etc., y podrás elegir fácilmente la correcta. Los arrays son una de las peores variables, ya que causan muchos problemas. El error más común que veo en los plugins de WordPress es "undefined index". Usar clases asegura que todas tus propiedades estén definidas cuando las necesites. Los registros conducen a una arquitectura más sucia y propensa a errores.

¿Hay algún plugin de WordPress de gran tamaño que no use una clase "God" que pueda ver en GitHub? Por lo que puedo ver, proveedores grandes como Skyverge y otros todavía usan la idea de la clase "God".

Buena pregunta, hay varios enfoques y depende de lo que quieras lograr.
Yo suelo hacer:
add_action( 'plugins_loaded', array( 'someClassy', 'init' ));
class someClassy {
public static function init() {
$class = __CLASS__;
new $class;
}
public function __construct() {
//aquí puedes construir lo que consideres adecuado...
}
//etc...
}
Un ejemplo más detallado y profundo que surgió como resultado de recientes discusiones sobre este mismo tema en la sala de chat puede verse en este gist del miembro de WPSE toscho.
El enfoque del constructor vacío.
Aquí hay un extracto de ventajas/desventajas tomado del gist mencionado que ejemplifica completamente el enfoque del constructor vacío.
Ventajas:
Las pruebas unitarias pueden crear nuevas instancias sin activar ningún hook automáticamente. No es un Singleton.
No se necesita una variable global.
Cualquiera que quiera trabajar con la instancia del plugin puede simplemente llamar a T5_Plugin_Class_Demo::get_instance().
Fácil de desactivar.
Sigue siendo OOP real: ningún método de trabajo es estático.
Desventaja:
- Quizás más difícil de leer.
En mi opinión, la desventaja es bastante débil, por lo que este sería mi enfoque favorito, aunque no el único que uso. De hecho, varios otros pesos pesados sin duda aportarán su perspectiva sobre este tema pronto, ya que hay buenas opiniones al respecto que deberían ser escuchadas.
nota: necesito encontrar el ejemplo del gist de toscho que analizaba 3 o 4 comparaciones sobre cómo instanciar una clase dentro de un plugin, examinando los pros y contras de cada uno, donde el enlace anterior era la forma preferida de hacerlo, pero los otros ejemplos proporcionan un buen contraste para este tema. Espero que toscho aún lo tenga archivado.
Nota: La respuesta de WPSE a este tema con ejemplos relevantes y comparaciones. También la mejor solución para instanciar una clase en WordPress.
add_shortcode( 'baztag', array( My_Plugin::get_instance(), 'foo' ) );
class My_Plugin {
private $var = 'foo';
protected static $instance = NULL;
public static function get_instance() {
// crear un objeto
NULL === self::$instance and self::$instance = new self;
return self::$instance; // devolver el objeto
}
public function foo() {
return $this->var; // ¡nunca usar echo o print en un shortcode!
}
}

¿cuál sería la diferencia entre add_action('plugins_loaded',...); y add_action('load-plugins.php',...); El ejemplo que tomé usó el último

No estoy seguro, pero considerando los nombres diría que el hook plugins_loaded
es una cola que se ejecuta después de que los plugins están cargados, mientras que el hook load-plugins.php
engancharía la cola real de carga de plugins. Prácticamente no hay mucha diferencia, pero teóricamente podría causar problemas si estás llamando métodos cuando no todos los plugins están cargados. Por eso preferiría plugins_loaded
. De nuevo, solo estoy interpretando sus nombres.

Por lo que entiendo, load-plugins.php, aunque funciona, está asociado con el archivo core update.php
y no es parte de las acciones predeterminadas habituales en las que se debería confiar cuando se trata de la secuencia de eventos que se disparan durante la inicialización, y por esa razón prefiero usar aquellos hooks que sí aplican, en este caso plugins_loaded
. Esto es a lo que a menudo me refiero como una instantánea rápida de lo que sucede cuando Action Reference. Mi explicación no es completa en su totalidad.

¿Estás buscando esta respuesta?

@toscho Sí, esa es. Recuerdo que lo vimos en el chat pero también en forma de un Gist. Pero es lo mismo. ¡Gracias por eso!

Muy útil, y el add_shortcode() parece ser una forma mucho más simple de incluir la salida de una parte particular de esa clase, en lugar de obtener una instancia por así decirlo. ¿Es posible usar un shortcode en los archivos de plantilla así como en el Editor de WordPress?

@kalpaitch Si un shortcode se ajusta a tu caso de uso, entonces sí, echo do_shortcode('[my-shortcode]');
en su forma más básica, para cuando quieras llamar a un shortcode fuera de la pantalla tradicional del editor de entradas, hará el truco.

Me gusta este enfoque tipo singleton. Sin embargo, cuestiono el uso de plugins_loaded como tu gancho de acción inicial. Este gancho está diseñado para ejecutarse después de que todos los plugins se hayan cargado. Al engancharte después, estás básicamente secuestrando ese gancho, y podrías encontrarte con conflictos o problemas en la secuencia de inicio con otros plugins o temas que se enganchen a plugins_loaded. Yo no usaría ningún gancho de acción para ejecutar tu método de inicialización. La arquitectura de plugins fue diseñada para ejecutarse en línea, no en una acción.

Normalmente me engancho a init
la mayoría de las veces, pero entiendo tu punto con plugins_loaded
, tiene perfecto sentido. También tiene sentido si tu intención era secuestrar ese gancho para ese propósito. No veo el uso de ganchos de la misma manera para plugins que tú, pero me interesaría aprender más sobre tu posición en el tema si tienes alguna lectura recomendada. ¿Enlaces? ¿Más ejemplos de qué tipo de problemas de secuencia podríamos encontrar? Gracias colega..

@Tom Auger, entiendo completamente tu punto sobre la carga usando el hook plugins_loaded. ¿Sería mejor usar el primer método de la pregunta para iniciar una clase, o si propones un método alternativo, podrías incluirlo en una respuesta?

Ten en cuenta que si usas register_activation_hook()
necesitas llamar a esa función antes de que se active la acción plugins_loaded
.

@Geert, de acuerdo, descubrí que estos hooks de activación aún tenían que ir en __construct

Como información adicional, consulta esta publicación de @mikeschinkel y la discusión en los comentarios. http://hardcorewp.com/2012/using-classes-as-code-wrappers-for-wordpress-plugins/#comment-149

Utilizo la siguiente estructura:
Prefix_Example_Plugin::on_load();
/**
* Ejemplo de carga inicial de plugin basado en clases.
*/
class Prefix_Example_Plugin {
/**
* Engancha init (nada más) y llama a cosas que necesitan ejecutarse inmediatamente.
*/
static function on_load() {
// si es necesario, el interruptor de apagado va aquí (si la constante de deshabilitar está definida, entonces retorna)
add_action( 'init', array( __CLASS__, 'init' ) );
}
/**
* Configuración adicional de hooks, carga de archivos, etc.
*
* Nota que para métodos enganchados, el nombre iguala al hook (cuando es posible).
*/
static function init( ) {
}
}
Notas:
- Tiene un lugar definido para cosas que necesitan ejecutarse inmediatamente
- Deshabilitar/anular para ajustes es fácil (desenganchar un método
init
) - No creo haber usado/necesitado nunca un objeto de la clase del plugin - requiere llevar un seguimiento del mismo, etc; esto es realmente un falso namespace por propósito, no OOP (la mayoría del tiempo)
Aviso legal No uso pruebas unitarias todavía (tantas cosas en mi plato) y escuché que lo estático puede ser menos preferible para ellas. Haz tu investigación sobre esto si necesitas hacer pruebas unitarias.

Sé que a las personas muy enfocadas en pruebas unitarias no les gustan las soluciones estáticas/singleton. Creo que si entiendes completamente lo que intentas lograr usando static y al menos estás consciente de las ramificaciones de hacerlo, entonces está perfectamente bien implementar tales métodos. Hay buenos temas sobre esto en [SO]

Esto me hizo pensar mucho. Entonces, ¿por qué usar Clases y no simplemente volver a funciones simples con prefijos? ¿Lo hacemos solo para tener nombres de funciones/métodos más limpios? Quiero decir, ¿tenerlos anidados con un "static" antes los hace mucho más legibles? La posibilidad de tener un conflicto de nombres es casi la misma que para un nombre de clase único si usas prefijos adecuados, ¿o me estoy perdiendo algo?

@JamesMitch sí, los métodos totalmente estáticos son básicamente solo funciones con un falso namespace como se usa en WP. Sin embargo, las clases tienen algunas ventajas sobre las funciones puras incluso en este caso, como el autoload y la herencia. Últimamente he estado pasando de métodos estáticos hacia objetos reales instanciados organizados por un contenedor de inyección de dependencias.

Buena idea, pero solo se puede usar en la clase principal del plugin. Uno de los mayores problemas de muchos plugins de WordPress es que cargan toneladas de código que nunca se usa en la solicitud. Para evitar cargar toneladas de código que realmente no se utiliza, tienes que dividir tu funcionalidad en fragmentos más pequeños usando espacios de nombres y clases, y luego utilizar la carga automática, que solo carga los archivos que realmente se usan. Este tipo de enfoque requiere un desarrollo más cuidadoso del plugin, para asegurarte de que tengas una lógica basada en solicitudes (el código se usa siempre para una solicitud y muere después de eso).

Todo depende de la funcionalidad.
Una vez hice un plugin que registraba scripts cuando se llamaba al constructor, así que tuve que engancharlo al hook wp_enqueue_scripts
.
Si quieres llamarlo cuando se carga tu archivo functions.php
, también podrías crear una instancia tú mismo $class_instance = new ClassName();
como mencionaste.
Podrías considerar la velocidad y el uso de memoria. No conozco ningún caso, pero puedo imaginar que hay hooks no llamados en algunas situaciones. Al crear tu instancia en ese hook podrías ahorrar algunos recursos del servidor.

Genial, gracias por eso. Supongo que hay dos puntos en la pregunta anterior también. El otro es si __construct es adecuado o si init() es una mejor manera de inicializar la clase.

Sé que esto tiene un par de años, pero mientras tanto php 5.3 soporta métodos anónimos, así que se me ocurrió esto:
add_action( 'plugins_loaded', function() { new My_Plugin(); } );
y de alguna manera es el que más me gusta. Puedo usar constructores regulares y no necesito definir ningún método "init" o "on_load" que desordene mis estructuras de POO.
