Usando herencia con WordPress

Como programador WordPress, estás siempre buscando mejoras maneras de reusar tu código entre tus proyectos. Tu tiempo es importante y no deseas reinventar la rueda cada vez comienzas un nuevo proyecto.

La programación orientada a objetos puede ayudarte con eso. En un artículo pasado, te expliqué por qué deberías aprenderlo. Ahora es el tiempo de empujar las cosas aun más y mirar la característica principal para reusar tu código. Es probable que lo hayas escuchado antes. Se llama “herencia“.

¿Qué es la herencia?

Herencia se trata de maximizar la reutilización de código entre tus clases. Te permite de crear una jerarquía entre ellas. Esa jerarquía crea una relación “como es” entre tus clases.

Es por eso que muchos ejemplos siguen la fórmula:

  • La clase Coche extiende Vehículo (Un coche es un vehículo)
  • La clase Perro extiende Animal (Un perro es un animal)

Pero esos ejemplos no son prácticos. Ellos sólo destacan este tipo de relación. La conclusión de esos ejemplos es que necesitas aprender a ver la relación entre tus clases. Una vez que eso sucede, serías capaz de usar la herencia para crear código reusable por tus proyectos.

Visibilidad revisitada

Cuando discutí el encapsulamiento, mencioné tres niveles de acceso posibles para las propiedades y los métodos. Esos fueron :

  • Public
  • Private
  • Protected

“Public” significaba que cualquier persona podía acceder una propiedad o un método. Mientras tanto, “Private” significaba el contrario. Sólo podías accederlo internamente.

Ahora, es el tiempo de elaborar sobre esas definiciones un poco más. Cuando usando herencia, definir una propiedad o un método como private lo limita a la clase que lo definía. Sus subclases no pueden accederlo. Mientras tanto, protected da acceso a toda la jerarquía de clase. Esto significa que subclases pueden acceder esa propiedad o ese método.

Las herramientas de herencia

Antes de ver un ejemplo práctico, vamos a repasar algunas de las herramientas que vas a usar con herencia. Una buena comprensión de esas herramientas te permitirá usar herencia a su potencial máximo.

Interfaces

Interfaces son las herramientas más básica de tu inventario. La mejor manera de ver una interfaz es como un contrato. Al implementar una interfaz, tu clase acepta de implementar métodos específicos. Sólo puedes definir métodos de tipo public en una interfaz.

Al sólo ofrecer un contrato, dejas otros preocuparse con la implementación. Todo que te importa es que los métodos en tu contrato existen. Miremos un pequeño ejemplo.

interface Post_Interfaz
{
    /**
     * Obtenga la clase CSS.
     *
     * @return string
     */
    public function obtenga_clase();
 
    /**
     * Obtenga el tipo de post.
     *
     * @return string
     */
    public function obtenga_tipo();
}

La interfaz Post_Interfaz representa un pequeño contrato por un post de WordPress. Tiene dos métodos : obtenga_clase y obtenga_tipo. Cualquier clase que implementa esa interfaz necesitará tener estos dos métodos.

Interfaces incorporada en PHP

PHP está lleno de interfaces útiles que puedes usar para hacer tu vida más fácil. Por ejemplo, Countable te permite pasar tu objeto a la función incorporada count.

class Posts implements Countable
{
    /**
     * @var Post_Interfaz[]
     */
    protected $posts;
 
    // ...
 
    public function count()
    {
        return count($this->posts);
    }
}
 
$posts = new Posts();
 
$num_posts = count($posts);

La clase Posts arriba contiene múltiples objetos implementando la interfaz Post_Interfaz. Porque Posts implementa la interfaz Countable, podemos pasar un objeto Posts a la función count. Esto nos da una manera fácil de obtener el número total de objetos Post_Interfaz en Posts.

Herencia de interfaces

Interfaces pueden heredar también de otros interfaces. Es el caso por la interfaz incorporada Traversable. Traversable te permite usar tu objeto en un lazo foreach, pero no puede usarlo directamente. Necesitas implementar ya sea la interfaz Iterator o IteratorAggregate. Esas son interfaces hijas de Traversable.

class Posts implements IteratorAggregate
{
    /**
     * @var Post_Interfaz[]
     */
    protected $posts;
 
    // ...
 
    public function getIterator()
    {
        return new ArrayIterator($this->posts);
    }
}
 
$posts = new Posts();
 
foreach ($posts as $post) {
    // Hacer cosas
}

Ese ejemplo muestra nuestra clase Posts implementando la interfaz IteratorAggregate. Eso dijo, hay una pequeña cosa que necesitamos elaborar sobre. Es que no podemos tener nuestro método getIterator devolver un array de objetos PostInterfaz.

Eso es por el contrato de la interfaz IteratorAggregate. Dice que el método getIterator necesita devolver un objeto implementando la interfaz Traversable. Por suerte para nosotros, hay una clase PHP diseñada para convertir un array en un objeto de tipo Traversable.

Eso es por qué getIterator devuelve un objeto de tipo ArrayIterator. Es una clase que convierte un array en un objeto de tipo Traversable. Ahora, podemos recorrer los posts en nuestra clase Posts.

Implementar múltiples interfaces

Mientras que una clase sólo puede tener una clase padre, puede implementar tantas interfaces que desea. Es una característica poderosa de las interfaces. Puedes hacer que una sola clase cumpla tantos contratos que desea.

class Posts implements Countable, IteratorAggregate
{
    /**
     * @var array
     */
    protected $posts;
 
    // ...
 
    public function count()
    {
        return count($this->posts);
    }
 
    public function getIterator()
    {
        return new ArrayIterator($this->posts);
    }
}

Por ejemplo, nuestra clase Posts ahora implementa ambos las interfaces Countable y IteratorAggregate. Esto te permite pasar un objeto Posts a la función count y usarlo en un lazo foreach. Ambos van a funcionar.

Clase abstracta

Imagine que quieres reusar tu código en tus clases, pero cada clase tiene una pequeña diferencia entre ellas. ¿No sería genial poder implementar una parte de la clase y necesitar que otras clases implementan el resto? ¡Bien!, eso es lo que clases abstractas hacen.

Ellas implementan parcialmente la lógica en una clase, pero deja algunos detalles por las subclases. Los métodos abstractos son los métodos que una subclase necesita implementar. Funcionan de la misma manera que un método definido en una interfaz. La única diferencia es que puedes definir un método abstracto como protected.

Una manera de ver una clase abstracta es como una interfaz donde ya has programado algunas de la lógica. Pero deberías saber que a causa de eso, no puedes instanciar clases abstractas. Son clases incompletas.

abstract class Abstract_Post implements Post_Interfaz
{
    /**
     * @var int
     */
    protected $id;
 
    /**
     * Obtenga la clase CSS.
     *
     * @return string
     */
    public function obtenga_clase()
    {
        return $this->obtenga_tipo() . '-' . $this->id;
    }
 
    abstract public function obtenga_tipo();
}

En el caso de la clase Abstract_Post que puedes ver encima, sabemos algunos detalles comunes para todas las clases. Todos los posts van a tener un post ID. También va a generar su clase CSS de la misma manera. El método obtenga_clase concatena el tipo del post y su ID juntos.

Pero no sabemos el tipo del post todavía. Es por qué el método obtenga_tipo es abstracto. Permitimos usar el método obtenga_tipo en el método obtenga_clase sin la necesidad de programarlo. Dejáremos eso a las clases concretas.

Clases concretas

Clases concretas son clases que tu código terminará usando. Podrían implementar una o más interfaces y extender una clase abstracta. Pero la cosa importante es que puedes instanciar estas clases.

class Attachment extends Abstract_Post
{
    /**
     * Obtenga el tipo del post.
     *
     * @return string
     */
    public function obtenga_tipo()
    {
        return 'attachment';
    }
}
 
$attachment = new Attachment();
 
$attachment->obtenga_clase();

Ahora, es el tiempo de programar el método obtenga_tipo. Para nuestra clase Attachment, la hacemos devolver attachment. Es el mismo valor que el tipo de post WordPress preexistente. Y porque hemos completado nuestra clase, podemos instanciarla y usarla.

Sobrescritura de métodos

Aquí es un guión : defines un método en tu clase padre. Funciona para el 90% de los usos, pero no en esa única subclase. ¿Qué puedes hacer? ¡Bien!, puedes sobrescribir el método.

Sobrescribes un método definiendo ese método otra vez en la subclase. Hacerlo reemplaza el antiguo y te permite añadir la lógica especifica que quería.

class Video extends Attachment
{
    /**
     * Obtenga el tipo del post.
     *
     * @return string
     */
    public function obtenga_tipo()
    {
        return 'video';
    }
}

En nuestra clase Video, sobrescribimos nuestro método obtenga_tipo. En lugar de devolver attachment, va a devolver video.

El método padre es todavía disponible si deseas usarlo. Todo que necesitas hacer es usar la palabra clave ´parent´. Esto es útil cuando solo quieres modificar el valor devuelto por el método padre.

class Cuadro extends Attachment
{
    /**
     * Obtenga el tipo del post.
     *
     * @return string
     */
    public function obtenga_tipo()
    {
        return 'cuadro-' . parent::get_type();
    }
}

En este ejemplo último, el método obtenga_tipo de la clase Cuadro no va a devolver attachment. En lugar, va a devolver cuadro-attachment. attachment viene de nuestra llamada al ´parent::get_type()´.

Palabra clave final

Es posible que haba algunos casos donde no deseas otras personas extender tu clase ni sobrescribir tu método. Esto es donde la palabra clave final puede ayudarte. Previene alguien de extender una clase o sobrescribir un método.

Rara vez hay un caso por una clase a estar final, pero es posible que veas métodos finales en algunos casos. En general, usas un método final cuando el método es crucial al funcionamiento de una clase. En ese caso, sobrescribir el método podría tener consecuencias terribles.

Asegurar de que recibes el buen tipo de objeto

¿Cómo puedes asegurar de que recibiste el tipo de objeto correcto en tu código? No quieres errores porque alguna persona no te da el objeto correcto. Veamos dos opciones que puedes usar para prevenir errores en tu código.

Declaraciones de tipo

Declaraciones de tipo es útil cuando usando herencia. Te permite forzar una función o un método a solo aceptar un parámetro de un tipo específico. Esto te evita el necesito hacer validación de tipo en tu código. Eso dijo, si tu función o método recibe un parámetro invalido, PHP va a generar un error fatal.

/**
 * Guardar el post en la base de datos.
 *
 * @param Post_Interfaz $post
 */
function guardar_post(Post_Interfaz $post)
{
    // Estas segura de que $post implementa Post_Interfaz
}

Validar en tu código

Puedes verificar también el tipo del objeto en tu código. PHP tiene dos maneras de hacerlo. Puede usar la función is_a o el operador instanceof. Esto es como a WordPress le gusta hacerlo porque permite codigar defensivamente. Usar esta forma de verificación previene errores fatales.

/**
 * Guardar el post en la base de datos.
 *
 * @param mixed $post
 */
function guardar_post($post)
{
    // No estas seguro que $post implementa Post_Interfaz
    
    if (!$post instanceof Post_Interfaz) {
        return;
    }
    
    // Estas seguro que $post implementa Post_Interfaz
}

El punto del ejemplo encima es de ver el estado antes y después en nuestro método guardar_post. Antes nuestra verificación utilizando el operador instanceof, nuesta variable post puede estar cualquier cosa. Pero después, estamos seguro que post implementa la interfaz Post_Interfaz.

¿Qué deberías verificar?

Esa es la mejor pregunta a preguntar. ¿Es el objeto del correcto tipo o implementa la interfaz? Si bien que es a ti de decidir, utilizar una interfaz permite el máximo de flexibilidad.

Cuando validas una clase, le digas a ellos cómo interactuar con tu código. Es una manera tener más control sobre tu código. Necesitan utilizar tu clase o no pueden hacer nada.

La validación utilizando una interfaz permite a otros de construir lo que quieren. No los fuerzas a utilizar tus clases. Solo los quieres utilizar el contrato que creaste.

Un ejemplo practico con WP_Widget

Los widgets de WordPress son excelentes ejemplos de cómo utilizar herencia. Hay una relación clara entre la clase WP_Widget y sus clases niñas. Esa relación distinta permite la reutilización una gran parte del código. Puedes ver el impacto de eso por la pequeña talla de las clases niñas.

Cómo funciona actualmente

Para desarrollar tu proprio widget personalizado, necesitas extender la clase WP_Widget y añadir tu propia lógica. WordPress espera que ti sobrescribir el método widget. Si no, va a generar un error fatal.

class WP_Widget_Ejemplo extends WP_Widget
{
    public function widget($args, $instance)
    {
        echo 'test';
    }
}

Puedes encontrar más información sobre la creación de widget en el codex.

Utilizar la herencia

Como un ejercicio, vamos a reescribir partes del código de manera de utilizar herencia. Esto debería darte una buena idea de cómo utilizar las herramientas descritas más temprano.

Definir una interfaz

Vamos a comenzar por definir la interfaz WP_Widget_Interfaz. Quieres crear un contrato de base para todos los widgets en WordPress. En haciendo así, establecemos cuales son los métodos que WordPress considera necesario para una clase widget.

interface WP_Widget_Interfaz
{
    /**
     * Inscribir el widget.
     */
    public function inscribir();
 
    /**
     * Echo el contenido del widget.
     *
     * @param array $args
     * @param array $instance
     */
    public function widget(array $args, array $instance);
}

Podemos ver que la interfaz WP_Widget_Interfaz solo contiene dos métodos. No es un error. Quieres limitar la interfaz a métodos requisitos solamente. Solo los métodos públicos que WordPress utiliza son inscribir y widget. Todos los otros métodos son internos.

Es importante mencionar que algunos métodos necesitan estar publico a causa de la naturaleza del sistema callback. Eso dijo, no deberían estar incluidos en la interfaz. WordPress no necesita saber que existen para cumplir tu contrato.a

Tomar un vistazo a la clase WP_Widget

Ahora que hemos definido una interfaz por los widgets, echemos un mejor vistazo a la clase WP_Widget. Vamos a retrabajarlo manteniendo el comportamiento esperado por WordPress.

#### Manejar la visibilidad de los métodos y propiedades

WP_Widget solo utiliza métodos y propiedades públicos. La documentación define métodos privados como métodos para usa interna solamente. Vamos a retrabajar la visibilidad de los métodos y propiedades. Eso es porque queremos ellos a seguir los principios de encapsulamiento.

Eso significa que solo unas pocas funciones son públicas. Hay métodos inscribir y widget. Definimos esos en la interfaz. También hay tres métodos callback que deberían estar públicos.

Todos los otros métodos son para usa interna solamente. Esto significa que deberías definir esos métodos como “protected”. Esto va para las propiedades también.

Utilizar una clase abstracta

La clase WP_Widget es una candidata ideal por una clase abstracta. WordPress quiere que cada objeto niño implementa su propio método widget. En utilizando una clase abstracta, estamos posponiendo la necesidad de implementar el método a la clase niña. ¡No necesite utilizar un die!

Utilizar la palabra clave final

La documentación de WP_Widget nos muestra los métodos que no deberían estar sobrescritos pero no dice más. Vamos a utilizar la palabra clave final para bloquear esos métodos. Estamos, otra vez, formalizando el comportamiento que WordPress espera.

El resultado

Puedes ver el resultado abajó. No es una copia exacta de todos los métodos. Solo destaca los cambios que discutimos más temprano. La documentación es en Inglés para imitar el código de WordPress.

abstract class Abstract_WP_Widget implements WP_Widget_Interface
{
    /**
     * @var array 
     */
    protected $widget_options;

    /**
     * @var string 
     */
    protected $id_base;

    /**
     * @var string 
     */
    protected $name;

    /**
     * @var array 
     */
    protected $control_options;

    /**
     * @var bool 
     */
    protected $number = false;

    /**
     * @var bool
     */
    protected $id = false;

    /**
     * @var bool 
     */
    protected $updated = false;
 
    /**
     * Constructor
     *
     * @param type $id_base
     * @param type $name
     * @param type $widget_options
     * @param type $control_options
     */
    public function __construct( $id_base, $name, $widget_options = array(), $control_options = array() )
    {
    }
 
    /**
     * {@inheritdoc}
     */
    final public function register()
    {
    }
 
    /**
     * Generate the actual widget content.
     */
    final public function display_callback($args, $widget_args = 1)
    {
    }
 
    /**
     * Generate the control form.
     */
    final public function form_callback($widget_args = 1)
    {
    }
 
    /**
     * Deal with changed settings.
     *
     * @param mixed $deprecated Not used.
     */
    final public function update_callback($deprecated = 1)
    {
    }
 
    /**
     * Echo the settings update form.
     *
     * @param array $instance Current settings
     */
    protected function form($instance)
    {
    }
 
    /**
     * Constructs name attributes for use in form() fields.
     *
     * @param string $field_name
     * 
     * @return string
     */
    protected function get_field_name($field_name) {
    }
 
    /**
     * Constructs id attributes for use in form() fields.
     *
     * @param string $field_name
     * 
     * @return string
     */
    protected function get_field_id($field_name) {
    }
 
    /**
     * Update a widget instance.
     *
     * @param array $new_instance
     * @param array $old_instance
     *
     * @return array
     */
    protected function update($new_instance, $old_instance)
    {
    }
}

Cambiar la validación en WordPress

Tenemos una interfaz adecuada para los widgets. Ahora podemos hacer cambios a la función the_widget. En lugar de validar utilizando la clase WP_Widget, vamos a utilizar la interfaz.

function the_widget($widget, $instance = array(), $args = array()) {
    global $wp_widget_factory;
 
    $widget_obj = $wp_widget_factory->widgets[$widget];
    if ( !is_a($widget_obj, 'WP_Widget_Interfaz') )
        return;
    
    // ...
    
    $widget_obj->widget($args, $instance);
}

Mientras tanto, la clase WP_Widget_Factory también debería validar el objeto para ver si es un WP_Widget. No lo hace. Aquí es una manera posible que podríamos manejarlo.

Todo se reduce a practicar

Herencia es una de las características más poderosas de la programación orientada a objetos. La cantidad de tiempo que salvas con la reutilización de código es increíble. Eso dijo, también puede ser la cosa más difícil de aprender para la nueva gente. Ser capaz de ver la relación entre tus clases toma tiempo y practico.

Deja un comentario

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