¿La mejor manera de iniciar una clase en un complemento WP?


90

He creado un complemento y, por supuesto, siendo yo, quería seguir un buen enfoque OO. Ahora lo que he estado haciendo es crear esta clase y luego, justo debajo, crear una instancia de esta clase:

class ClassName {

    public function __construct(){

    }
}

$class_instance = new ClassName();  

Supongo que hay una forma más WP de iniciar esta clase, y luego me encontré con personas diciendo que prefieren tener una init()función que una __construct(). Y de manera similar, encontré algunas personas que usan el siguiente gancho:

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 WP en carga y tener esto como una variable accesible globalmente?

NOTA: Como un punto lateral interesante, he notado que si bien register_activation_hook()se puede llamar desde dentro __construct, no se puede llamar desde dentro init()usando el segundo ejemplo. Quizás alguien pueda iluminarme sobre este punto.

Editar: Gracias por todas las respuestas, claramente hay un poco de debate sobre cómo manejar la inicialización dentro de la clase en sí, pero creo que generalmente hay un consenso bastante bueno sobre add_action( 'plugins_loaded', ...);cuál es la mejor manera de comenzar ...

Editar: solo para confundir las cosas, también he visto esto usado (aunque no usaría este método yo mismo porque convertir una clase de OO en una función parece derrotar el punto):

// Start up this plugin
add_action( 'init', 'ClassName' );
function ClassName() {
    global $class_name;
    $class_name = new ClassName();
}

1
Con respecto a la última edición, si está contenida en el mismo archivo de complemento que la clase, se vuelve algo inútil. También puede crear una instancia de la clase según el método que describo. Incluso si está en un archivo separado, todavía es algo inútil. El único caso de uso que puedo ver es si desea crear una función de contenedor que le permita crear una instancia de una clase fuera de sus archivos de complementos, dentro de temas, etc. Aun así, tendría que preguntar qué lógica hay detrás de eso porque el uso adecuado de los condicionales y los ganchos debería permitir un control de grano fino sobre la creación de instancias, lo que le permite centrarse en usar el complemento.
Adam

Estoy un poco de acuerdo con esto, pero pensé que valía la pena ponerlo ya que lo encontré en un par de complementos de WP.
kalpaitch

Respuestas:


60

Buena pregunta, hay una serie de enfoques y depende de lo que quieras lograr.

A menudo lo hago;

add_action( 'plugins_loaded', array( 'someClassy', 'init' ));

class someClassy {

    public static function init() {
        $class = __CLASS__;
        new $class;
    }

    public function __construct() {
           //construct what you see fit here...
    }

    //etc...
}

A más a fondo un ejemplo en profundidad, que se produjo como resultado de algunas discusiones recientes sobre este mismo tema dentro de la sala de chat se puede ver en esta GIST por miembro WPSE toscho .

El enfoque del constructor vacío.

Aquí hay un extracto de ventajas / desventajas tomado de la esencia anterior que ejemplifica el enfoque del constructor vacío en su totalidad.

  • Ventajas:

    • Las pruebas unitarias pueden crear nuevas instancias sin activar ningún gancho automáticamente. No Singleton

    • No se necesita una variable global.

    • Quien quiera trabajar con la instancia del complemento puede llamar a T5_Plugin_Class_Demo :: get_instance ().

    • Fácil de desactivar.

    • Todavía POO real: ningún método de trabajo es estático.

  • Desventaja:

    • Tal vez más difícil de leer?

La desventaja en mi opinión es débil, por lo que tendría que ser mi enfoque favorito, sin embargo, no es el único que uso. De hecho, varios otros pesos pesados ​​sin duda intervendrán en este tema con su opinión sobre el tema en breve porque hay algunas buenas opiniones sobre este tema que deberían expresarse.


nota: necesito encontrar el ejemplo esencial de toscho que realizó 3 o 4 comparaciones de cómo crear una instancia de una clase dentro de un complemento que analizó los pros y los contras de cada uno, que el enlace anterior era la forma preferida de hacerlo, pero Los otros ejemplos proporcionan un buen contraste con este tema. Esperemos que toscho todavía tenga eso en el archivo.

Nota: La respuesta de WPSE a este tema con ejemplos y comparaciones relevantes. También la mejor solución, por ejemplo, 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() {

        // create an object
        NULL === self::$instance and self::$instance = new self;

        return self::$instance; // return the object
    }

    public function foo() {

        return $this->var; // never echo or print in a 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
kalpaitch el

1
Por lo que entiendo, load-plugins.php, aunque funciona, está asociado con el update.phparchivo central y no es parte de las acciones predeterminadas habituales en las que se debe confiar cuando se trata de la secuencia de eventos que se activan durante la inicialización y por eso prefiero usar esos ganchos que se 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 está completa en su totalidad.
Adam

44
Me gusta este enfoque tipo singleton. Sin embargo, cuestiono usar plugins_loaded como su gancho de acción de inicialización. Este enlace debe ejecutarse después de que se hayan cargado todos los complementos. Al engancharse después, estás secuestrando ese gancho y puedes divertirte en conflictos o problemas de secuencia de inicio con otros complementos o temas que se conectan a plugins_loaded. No conectaría ninguna acción para ejecutar su método de inicialización. La arquitectura del complemento fue diseñada para ejecutarse en línea, no en una acción.
Tom Auger el

2
Tenga en cuenta que si usa register_activation_hook()debe llamar a esa función antes de que se active la plugins_loadedacción.
Geert

1
Como información adicional, vea esta publicación de @mikeschinkel y el dicuss en los comentarios. hardcorewp.com/2012/…
bueltge

78

Al llegar aquí exactamente 2 años después de la pregunta original, hay algunas cosas que quiero señalar. (No me pidas que señale muchas cosas , nunca).

Gancho adecuado

Para crear una instancia de una clase de complemento, se debe usar el gancho adecuado . No hay una regla general para lo que es, porque depende de lo que haga la clase.

Usar un gancho muy temprano a "plugins_loaded"menudo no tiene sentido porque un gancho como ese se dispara para solicitudes de administrador, frontend y AJAX, pero muy a menudo un gancho posterior es mucho mejor porque permite instanciar clases de complementos solo cuando es necesario.

Por ejemplo, se puede crear una instancia de una clase que hace cosas para plantillas "template_redirect".

En términos generales, es muy raro que una clase necesite ser instanciada antes de "wp_loaded"ser despedida.

No hay clase de Dios

La mayoría de todas las clases usadas como ejemplos en respuestas anteriores usan una clase llamada como "Prefix_Example_Plugin"o "My_Plugin"... Esto indica que probablemente haya una clase principal para el complemento.

Bueno, a menos que un complemento esté hecho por una sola clase (en cuyo caso nombrarlo después del nombre del complemento es absolutamente razonable), crear una clase que administre todo el complemento (por ejemplo, agregar todos los ganchos que necesita un complemento o crear instancias de todas las otras clases de complemento ) puede considerarse una mala práctica, como un ejemplo de un objeto dios .

En la programación orientada a objetos, el código debe tender a ser SÓLIDO donde la "S" significa "Principio de responsabilidad única" .

Significa que cada clase debe hacer una sola cosa. En el desarrollo de plugins de WordPress significa que los desarrolladores deben evitar usar un solo gancho para crear una instancia de una clase de complemento principal , pero se deben usar diferentes ganchos para crear instancias de diferentes clases, de acuerdo con la responsabilidad de la clase.

Evitar ganchos en constructor

Este argumento se ha introducido en otras respuestas aquí, sin embargo, quiero comentar este concepto y vincular esta otra respuesta donde se ha explicado ampliamente en el ámbito de las pruebas unitarias.

Casi 2015: PHP 5.2 es para zombies

Desde el 14 de agosto de 2014, PHP 5.3 llegó a su fin . Definitivamente está muerto. PHP 5.4 será compatible para todo 2015, significa un año más en el momento en que estoy escribiendo.

Sin embargo, WordPress aún admite PHP 5.2, pero nadie debería escribir una sola línea de código que admita esa versión, especialmente si el código es OOP.

Hay diferentes razones:

  • PHP 5.2 murió hace mucho tiempo, no se lanzaron correcciones de seguridad, lo que significa que no es seguro
  • PHP 5.3 agregó 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 de forma gratuita?

Si no desea usar el código PHP 5.4+, use al menos 5.3+

Ejemplo

En este punto, es hora de revisar las respuestas anteriores basadas en lo que dije hasta aquí.

Una vez que ya no tengamos que preocuparnos por 5.2, podemos y debemos usar espacios de nombres.

En aras de una mejor explicación del principio de responsabilidad única, mi ejemplo utilizará 3 clases, una que hace algo en el frontend, una en el backend y una tercera utilizada en ambos casos.

Clase de administrador:

namespace GM\WPSE\Example;

class AdminStuff {

   private $tools;

   function __construct( ToolsInterface $tools ) {
     $this->tools = $tools;
   }

   function setup() {
      // setup class, maybe add hooks
   }

}

Clase frontend:

namespace GM\WPSE\Example;

class FrontStuff {

   private $tools;

   function __construct( ToolsInterface $tools ) {
     $this->tools = $tools;
   }

   function setup() {
      // setup class, maybe add hooks
   }

}

Interfaz de herramientas:

namespace GM\WPSE\Example;

interface ToolsInterface {

   function doSomething();

}

Y una clase de Herramientas, utilizada por los otros dos:

namespace GM\WPSE\Example;

class Tools implements ToolsInterface {

   function doSomething() {
      return 'done';
   }

}

Al tener estas clases, puedo crear instancias con los ganchos 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; // this is not ideal, reason is explained below
   $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; // this is not ideal, reason is explained below
   $front_stuff = new GM\WPSE\Example\FrontStuff( $tools );    
} );

Inversión de dependencia e inyección de dependencia

En el ejemplo anterior, utilicé espacios de nombres y funciones anónimas para crear instancias de diferentes clases en diferentes ganchos, poniendo en práctica lo que dije anteriormente.

Observe cómo los espacios de nombres permiten crear clases nombradas sin ningún prefijo.

Apliqué otro concepto que se mencionó indirectamente anteriormente: Inyección de dependencias , es un método para aplicar el Principio de inversión de dependencias , la "D" en acrónimo SÓLIDO.

La Toolsclase se "inyecta" en las otras dos clases cuando se instancian, por lo que de esta manera es posible separar la responsabilidad.

Además, AdminStuffy FrontStuffclases utilizan tipo dando a entender que declarar que necesitan una clase que implementa ToolsInterface.

De esta manera, nosotros o los usuarios que usan 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 Dependencia.

Sin embargo, el ejemplo anterior se puede mejorar aún más. A ver cómo.

Cargador automático

Una buena manera de escribir un código OOP mejor legible es no mezclar la definición de tipos (Interfaces, Clases) con otro código y colocar cada tipo en su propio archivo.

Esta regla también es uno de los estándares de codificación PSR-1 1 .

Sin embargo, al hacerlo, antes de poder usar una clase, se necesita el archivo que la contiene.

Esto puede ser abrumador, pero PHP proporciona funciones de utilidad para cargar automáticamente una clase cuando es necesario, utilizando una devolución de llamada que carga un archivo en función de su nombre.

Usar espacios de nombres se vuelve muy fácil, porque ahora es posible hacer coincidir la estructura de la carpeta con la estructura del espacio de nombres.

Eso no solo es posible, sino que también es otro estándar de PSR (o mejor 2: PSR-0 ahora en desuso y PSR-4 ).

Siguiendo esos estándares, es posible utilizar diferentes herramientas que manejan la carga automática, sin tener que codificar un cargador automático personalizado.

Tengo que decir que los estándares de codificación de WordPress tienen diferentes reglas para nombrar archivos.

Entonces, al escribir código para el núcleo de WordPress, los desarrolladores deben seguir las reglas de WP, pero al escribir código personalizado es una elección del desarrollador, pero usar el estándar PSR es más fácil de usar herramientas ya escritas 2 .

Patrones de acceso global, registro y localizador de servicios.

Uno de los mayores problemas al crear instancias de clases de complementos en WordPress es cómo acceder a ellas desde varias partes del código.

WordPress en sí usa el enfoque global : las variables se guardan en un alcance global, haciéndolas accesibles en todas partes. Cada desarrollador de WP escribe la palabra globalmiles de veces en su carrera.

Este es también el enfoque que utilicé para el ejemplo anterior, pero es malo .

Esta respuesta ya es demasiado larga para permitirme explicar más por qué, pero leer los primeros resultados en el SERP para "variables globales mal" es un buen punto de partida.

Pero, ¿cómo es posible evitar las variables globales?

Hay diferentes formas

Algunas de las respuestas anteriores 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 bueno, pero obliga a implementar el patrón para cada clase a la que queremos acceder.

Además, muchas veces este enfoque pone el camino para caer en el problema de la clase de dios, porque los desarrolladores hacen accesible una clase principal usando este método, y luego lo usan para acceder a todas las demás clases.

Ya expliqué lo mala que es una clase de dios, por lo que el enfoque de instancia estática es un buen camino cuando un complemento solo necesita hacer accesibles una o dos clases.

Esto no significa que se pueda usar solo para complementos que tengan solo un par de clases, de hecho, cuando el principio de inyección de dependencia se usa correctamente, es posible crear aplicaciones bastante complejas sin la necesidad de hacer que un número global sea accesible. de objetos.

Sin embargo, a veces los complementos deben hacer accesibles algunas clases, y en ese caso el enfoque de instancia estática es abrumador.

Otro enfoque posible es usar el patrón de registro .

Esta es una implementación muy simple:

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;
   }

}

Con esta clase, es posible almacenar objetos en el objeto de registro mediante una identificación, por lo que tener acceso a un registro es posible tener acceso a todos los objetos. Por supuesto, cuando se crea un objeto por primera vez, debe agregarse al registro.

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 en claro que para ser útil, el registro debe ser accesible a nivel mundial. Una variable global para el registro único no es muy mala, sin embargo, para los puristas no globales es posible implementar el enfoque de instancia estática para un registro, o tal vez 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, creará una instancia del registro, en llamadas posteriores solo lo devolverá.

Otro método específico de WordPress para hacer que una clase sea accesible globalmente es devolver una instancia de objeto desde un filtro. Algo como esto:

$registry = new GM\WPSE\Example\Registry;

add_filter( 'gm_wpse_example_registry', function() use( $registry ) {
  return $registry;
} );

Después de eso, en todas partes se necesita el registro:

$registry = apply_filters( 'gm_wpse_example_registry', NULL );

Otro patrón que se puede usar es el patrón de localización de servicios . Es similar al patrón de registro, pero los localizadores de servicios se pasan a varias clases mediante inyección de dependencia.

El principal problema con este patrón es que oculta las dependencias de las clases, lo que dificulta el mantenimiento y la lectura del código.

DI contenedores

No importa el método utilizado para hacer que el localizador de registro o servicio sea accesible globalmente, los objetos deben almacenarse allí, y antes de almacenarse deben ser instanciados.

En aplicaciones complejas, donde hay muchas clases y muchas de ellas tienen varias dependencias, la creación de instancias de clases requiere una gran cantidad de código, por lo que aumenta la posibilidad de errores: el código que no existe no puede tener errores.

En los últimos años aparecieron algunas bibliotecas PHP que ayudan a los desarrolladores PHP a crear instancias y almacenar fácilmente instancias de objetos, resolviendo automáticamente sus dependencias.

Estas bibliotecas se conocen como Contenedores de inyección de dependencias porque son capaces de crear instancias de clases que resuelven dependencias y también de almacenar objetos y devolverlos cuando sea necesario, actuando de manera similar a un objeto de registro.

Por lo general, cuando se usan contenedores DI, los desarrolladores deben configurar las dependencias para cada clase de la aplicación, y luego, la primera vez que se necesita una clase en el código, se instancia con las dependencias adecuadas y la misma instancia se devuelve una y otra vez en las solicitudes posteriores. .

Algunos contenedores DI también son capaces de descubrir dependencias automáticamente sin configuración, pero utilizando la reflexión PHP .

Algunos contenedores DI conocidos son:

y muchos otros.

Quiero señalar que para complementos simples, que involucran solo unas pocas clases y las clases no tienen muchas dependencias, probablemente no valga la pena usar contenedores DI: el método de instancia estática o un registro global accesible son buenas soluciones, pero para complementos complejos El beneficio de un contenedor DI se hace evidente.

Por supuesto, incluso los objetos del contenedor DI deben ser accesibles para ser utilizados en la aplicación y para ese propósito es posible usar uno de los métodos vistos anteriormente, variable global, variable de instancia estática, devolver el objeto a través del filtro, etc.

Compositor

Usar el contenedor DI a menudo significa usar código de terceros. Hoy en día, en PHP, cuando necesitamos usar una biblioteca externa (no solo contenedores DI, sino cualquier código que no sea parte de la aplicación), simplemente descargarlo y ponerlo en nuestra carpeta de aplicaciones no se considera una buena práctica. Incluso si somos los autores de esa otra pieza de código.

Desacoplar un código de aplicación de dependencias externas es señal de una mejor organización, una mayor confiabilidad y una mejor cordura del código.

Composer , es el estándar de facto en la comunidad PHP para administrar dependencias PHP. Lejos de ser una corriente principal en la comunidad WP también, es una herramienta que todo desarrollador de PHP y WordPress debería al menos saber, si no usar.

Esta respuesta ya tiene el tamaño de un libro para permitir una discusión más profunda, y también discutir sobre Composer aquí probablemente esté fuera de tema, solo se mencionó por razones de integridad.

Para obtener más información, visite el sitio de Composer y también vale la pena leer este minisitio curado por @Rarst .


1 PSR son reglas estándar de PHP publicadas por PHP Framework Interop Group

2 Composer (una biblioteca que se mencionará en esta respuesta), entre otras cosas, también contiene una utilidad de autocargador.


1
Una nota adicional, que PHP 5.3 también es el final de la vida. Un anfitrión responsable ofrecerá al menos 5.4 como opción, si no por defecto
Tom J Nowell

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

3
Solo me pregunto, ¿cómo señalarías "muchas cosas" ? ;-)
MikeSchinkel

En un esfuerzo por evitar las variables globales, me gusta la idea del patrón de Registro con función y variable estática. La primera vez que se llama a la función, creará una instancia del registro, en las llamadas posteriores solo lo devolverá.
Michael Ecklund

10

Yo uso la siguiente estructura:

Prefix_Example_Plugin::on_load();

/**
 * Example of initial class-based plugin load.
 */
class Prefix_Example_Plugin {

    /**
     * Hooks init (nothing else) and calls things that need to run right away.
     */
    static function on_load() {

        // if needed kill switch goes here (if disable constant defined then return)

        add_action( 'init', array( __CLASS__, 'init' ) );
    }

    /**
     * Further hooks setup, loading files, etc.
     *
     * Note that for hooked methods name equals hook (when possible).
     */
    static function init(  ) {


    }
}

Notas:

  • ha definido el lugar para las cosas que necesitan correr de inmediato
  • deshabilitar / anular ajustes es fácil (desenganchar un initmétodo)
  • No creo que alguna vez haya usado / necesitado el objeto de la clase de complemento, requiere hacer un seguimiento de él, etc. este es realmente un espacio de nombres falso por propósito, no OOP (la mayoría de las veces)

Descargo de responsabilidad Todavía no uso pruebas unitarias (hay tantas cosas en myplate ) y escucho que la estática puede ser menos preferible para ellas. Investigue sobre esto si necesita realizar una prueba unitaria.


3
Sé que a las personas grandes en pruebas unitarias realmente no les gustan las soluciones estáticas / singleton. Creo que si comprende completamente lo que está tratando de lograr mediante el uso de estática y al menos es consciente de las ramificaciones de hacerlo, entonces está perfectamente bien implementar dichos métodos. Buenos temas que rodean esto en Stack Overflow
Adam

Esto me hizo pensar realmente. Entonces, ¿por qué usar una clase entonces y no solo volver a las funciones prefijadas simples? ¿Hacemos esto solo para tener nombres de funciones / métodos más limpios? Me refiero a tenerlos anidados con un "estático" b4 ¿es mucho más legible? La posibilidad de tener un conflicto de nombre es casi la misma que para un nombre de clase única si usa prefijos propios o me falta algo.
James Mitch

1
@JamesMitch sí, los métodos totalmente estáticos son principalmente funciones con espacio de nombres falso como se usa en WP. Sin embargo, las clases tienen algunas ventajas sobre las funciones puras, incluso en este caso, como la carga automática y la herencia. Últimamente me he movido de métodos estáticos a objetos reales instanciados organizados por contenedor de inyección de dependencia.
Rarst

3

Todo depende de la funcionalidad.

Una vez hice un complemento que registraba scripts cuando se llamó al constructor, así que tuve que conectarlo al wp_enqueue_scriptsgancho.

Si desea llamarlo cuando functions.phpse carga su archivo, también puede crear una instancia usted mismo $class_instance = new ClassName();como lo mencionó.

Es posible que desee considerar la velocidad y el uso de la memoria. No conozco ninguno, pero puedo imaginar que hay ganchos sin llamar en algunos casos. Al crear su instancia en ese enlace, puede guardar 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.
kalpaitch

1
Bueno, optaría por un init()método estático para que la instancia de clase se llame en el ámbito de la clase en lugar de otro ámbito donde posiblemente podría sobrescribir las variables existentes.
Tim S.

0

Sé que esto tiene un par de años, pero mientras tanto, php 5.3 admite métodos anónimos , así que se me ocurrió esto:

add_action( 'plugins_loaded', function() { new My_Plugin(); } );

y de alguna manera me gusta más. Puedo usar constructores regulares y no necesito definir ningún método "init" u "on_load" que estropee mis estructuras OOP.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.