Principio de responsabilidad única y WordPress

Para desarrolladores WordPress, puede ser difícil de mejorar tus habilidades PHP. No hay muchos recursos ni tutoriales para ayudarte aprender esos conceptos. Eso fue el problema de Nathaniel cuando pidió ayuda sobre Stack Overflow. (La pregunta es en inglés.)

Estaba buscando ayuda para aplicar el principio de responsabilidad única con WordPress. Como desarrolladores WordPress, podrías haber comenzado a trabajar o investigar la programación orientada a objetos. Posiblimente escuchaste de una cosa llamada SOLID.

Como tema, SOLID puede ser muy intimidante. Pero el principio de responsabilidad única solo es una parte de ello. (Es el “S” en SOLID.) Eso lo hace un tema más manejable a explicar y ayudarte con.

¿Cuál es el principio de responsabilidad única?

El principio de responsabilidad única es fácil a explicar. La clase que creabas debería hacer una cosa y solamente una cosa. También debería tener toda la información que necesita internamente para hacer su trabajo. Eso significa que no deberías usar globals.

¿Por qué es tan difícil de hacer con WordPress?

La realidad es que WordPress no fue creado con clases ni la programación orientada a objetos en mente. En lugar, los desarrolladores construyeron WordPress con APIs simples que son fáciles a utilizar. Esas son excelentes por los nuevos desarrolladores PHP.

Tal vez, es como comenzaste a usar PHP o aprendiste a programar. Eso dijo, estas APIs pueden ser confusas cuando estás intentando de aplicar los conceptos de la programación orientada a objetos. Eso es especialmente verdad por el principio de responsabilidad única.

Lo que WordPress te dice de hacer

¿Ya has comenzado a trabajar con la programación orientada a objetos? Si es así, es posible que hagas programado un plugin donde todo el código está en la clase. Es bastante común para los desarrolladores de hacerlo.

Es porque lo hace fácil para ti utilizar los espacios de nombres y prevenir errores. Incluso es recomendado en la documentación oficial de WordPress. Pero construir tu plugin de esa manera es problemático. Te deja con una clase enorme que contiene todas tus funciones.

Hacer cosas de esa manera no te ofrece cualquier ventaja sobre solo usando un prefijo de función. También no sigue el principio de responsabilidad única. Tu clase no tiene propósito porque hace todo.

Apliquemos el principio de responsabilidad única

¡Ahora que cubrimos un poco de las bases iniciales, comencemos a mirar el resto! Vamos a usar el principio de responsabilidad única para construir un plugin WordPress. Soy un gran fanático de los memes así vamos a crear un pequeño WP Meme Shortcode plugin como ejemplo.

Dividiendo tus clases por responsabilidad

Así para crear eso plugin, necesitas pensar en las tareas que ello necesita hacer. En este caso, no tienes que preocuparte demasiado. Para esto, vamos a dividir las tareas en cuatro partes.

Un punto de entrada

En primera vez, necesitas una clase que actúe como un punto de entrada por tu plugin. Es el punto de partida por WordPress cuando carga tu plugin. Debería manejar la inscripción de todos los componentes del plugin. También está a cargo de buscar toda la información que el plugin necesita para funcionar.

Es importante mencionar que la tarea del punto de entrada no es inscribir todos los hooks (o ganchos) del plugin. Esto va más allá de la responsabilidad de esta clase. Cómo manejar los hooks es suficientemente importante que vaya a tener su propia sección adelante en el articulo.

Normalmente, llamo esta clase Plugin. Esto podría parecer como una decisión de nombrar extraña para ti. ¡Pero hay una buena razón! La idea es que, si WordPress necesita saber de sola una clase, Plugin sería el nombre lógico a utilizar en ese contexto.

namespace WPMemeShortcode;
 
class Plugin
{
    /**
     * Cargar el plugin en WordPress.
     */
    public static function cargar()
    {
        // ...
    }
}

Por ahora, nuestra clase Plugin es más o menos vida. Ponemos un método estático llamado cargar. Pero no tiene código en este momento. La tarea del método cargar es cargar el plugin en WordPress para nosotros.

Administrar opciones

Deseas una sola clase tenga la responsabilidad de recuperar y guardar los opciones de tu plugin. Esto elimina la necesidad de otras partes de tu plugin de manejar sus opciones. Todo que necesitan hacer es interactuar con tu clase para obtener la opción que necesitan.

Cuando solo tienes pocas opciones, crear una clase por las opciones podría parecer como mucho. Así, si tuvieras que combinar esa responsabilidad con otra clase, debería ser la clase Plugin. Pero aún deberías practicar dividirla en una clase separada aun para pequeño plugins.

namespace WPMemeShortcode;
 
class Opciones
{
    /**
     * @var array
     */
    private $opciones;
 
    /**
     * Constructor.
     *
     * @param array $opciones
     */
    public function __construct(array $opciones = array())
    {
        $this->opciones = $opciones;
    }
 
    /**
     * Obtenga la opción con el nombre dado. Devuelve el valor predeterminado 
     * si el valor no existe.
     *
     * @param string $nombre
     * @param mixed  $predeterminado
     *
     * @return mixed
     */
    public function obtener($nombre, $predeterminado = null)
    {
        if (!$this->has($nombre)) {
            return $predeterminado;
        }
 
        return $this->opciones[$nombre];
    }
 
    /**
     * Compruebe que la opción existe o no.
     *
     * @param string $nombre
     *
     * @return bool
     */
    public function hay($nombre)
    {
        return isset($this->opciones[$nombre]);
    }
 
    /**
     * Guarde la opción. Remplaza la opción existente si el nombre ya está en uso.
     *
     * @param string $nombre
     * @param mixed  $valor
     */
    public function guardar($nombre, $valor)
    {
        $this->opciones[$nombre] = $valor;
    }
}

Diseñamos nuestra clase Opciones alrededor de una idea especifica. Es que vamos a guardar todas nuestras opciones en un solo vector. Todos nuestros métodos interactúan con ello.

Una página admin

Mediante el uso de la API settings, es bastante sencillo de crear una clase para manejar tu(s) página(s) admin. La clase manejaría la inscripción de todos los settings. También iría en cargo de la representación de estos settings.

namespace WPMemeShortcode;
 
class PaginaAdmin
{
    /**
     * @var Opciones
     */
    private $opciones;
 
    /**
     * Constructor.
     *
     * @param Opciones $opciones
     */
    public function __construct(Opciones $opciones)
    {
        $this->opciones = $opciones;
    }
 
    /**
     * Añadir la página admin al menú.
     */
    public function anadirPaginaAdmin()
    {
        add_options_page(__('WordPress Meme Shortcode', 'wp_meme_shortcode'), __('Meme Shortcode', 'wp_meme_shortcode'), 'install_plugins', 'wp_meme_shortcode', array($this, 'representar'));
    }
 
    /**
     * Configurar la página de opciones usando la API settings.
     */
    public function configurar()
    {
        // Inscribir settings
        register_setting('wp_meme_shortcode', 'wp_meme_shortcode');
 
        // Sección general
        add_settings_section('wp_meme_shortcode_general', __('General', 'wp_meme_shortcode'), array($this, 'representarSeccionGeneral'), 'wp_meme_shortcode');
        add_settings_field('wp_meme_shortcode_talla', __('Talla de imagen predeterminada', 'wp_meme_shortcode'), array($this, 'representarCajaTalla'), 'wp_meme_shortcode', 'wp_meme_shortcode_general');
    }
 
    /**
     * Representar la página admin usando la API settings.
     */
    public function representar()
    {
        ?>
        <div class="wrap" id="wp-meme-shortcode-admin">
            <h2><?php _e('WordPress Meme Shortcode', 'wp_meme_shortcode'); ?></h2>
            <form action="options.php" method="POST">
                <?php settings_fields('wp_meme_shortcode'); ?>
                <?php do_settings_sections('wp_meme_shortcode'); ?>
                <?php submit_button(); ?>
            </form>
        </div>
        <?php
    }
 
    /**
     * Representar la sección general.
     */
    public function representarSeccionGeneral()
    {
        ?>
        <p><?php _e('Configurar WordPress Meme Shortcode.', 'wp_meme_shortcode'); ?></p>
        <?php
    }
 
    /**
     * Representar la caja de talla.
     */
    public function representarCajaTalla()
    {
        ?>
        <input id="wp_meme_shortcode_talla" name="wp_meme_shortcode[talla]" type="number" value="<?php echo $this->options->get('talla', '500'); ?>" />
        <?php
    }
}

Si tienes múltiple página admin, sería apropiado para ti dividir cada página en su propia clase. También podrías usar herencia y poner el código común a todas las páginas admin en una clase padre.

El shortcode

En la misma manera que tu página admin, tu clase shortcode vaya a ser interactuando con la API shortcode de WordPress. Vaya a manejar la inscripción del shortcode y generar el rendimiento.

namespace WPMemeShortcode;
 
class Shortcode
{
    /**
     * @var Opciones
     */
    private $opciones;
 
    /**
     * Constructor.
     *
     * @param Opciones $opciones
     */
    public function __construct(Opciones $opciones)
    {
        $this->opciones = $opciones;
    }
 
    /**
     * Manejar el rendimiento del shortcode.
     *
     * @param array  $atributos
     * @param string $contenido
     */
    public function manejar(array $atributos, $contenido = null)
    {
        // Haga nada si ID no está dado o no es numérico
        if (!isset($atributos['id']) || !is_numeric($atributos['id'])) {
            return $contenido;
        }
 
        // Si talla no está dada o no es numérica, obtenga la talla predeterminada
        if (!isset($atributos['talla']) || !is_numeric($atributos['talla'])) {
            $atributos['talla'] = $this->opciones->get('talla', '500');
        }
 
        return "<img src=\"http://cdn.memegenerator.net/instances/{$atributos['size']}x/{$atributos['id']}.jpg\" />";
    }
}

Manejando hooks

Ya sea que estes trabajando sobre un tema o plugin WordPress, necesitas usar hooks. Es fundamental a WordPress sí mismo. Ahora, si estás intentando de progresar en tu comprensión de la programación orientada a objetos, aquí es donde podrías tener dificultad. ¿Que haces con los hooks cuando usas clases para contener tu código?

Lo que ves normalmente

Las dos maneras comunas que la gente trata con los hooks son por :

  • Poner todos los hooks en el constructor.
  • Crear un nuevo objeto y usar ese objeto para inscribir todos tus hooks al exterior de la clase.

Ninguna de las dos opciones es tan buena. Crear un objeto no debería añadir los hooks por defecto. Podría tener situaciones (como testing) donde quieres crear el objeto sin añadir los hooks. Pero, al tener el código de los hooks al exterior de la clase, estás robando la clase de una parte crítica de su responsabilidad.

Usar un método estático

La mejor manera de solucionar este problema es por usar un método estático como constructor personalizado. El método crea una instancia de tu clase y la usa para inscribir los hooks apropiados. Ese te permite separar la inscripción de tus hooks de la creación de tu objeto. Normalmente, llamo el método inscribir o cargar.

namespace WPMemeShortcode;

class PaginaAdmin
{
    /**
     * Inscribir la página admin con todos los hooks apropiados.
     *
     * @param Opciones $opciones
     */
    public static function inscribir( Opciones $opciones)
    {
        $pagina = new self($opciones);

        add_action('admin_init', array($pagina, 'configurar'));
        add_action('admin_menu', array($pagina, 'anadirPaginaAdmin'));
    }

    // ...

}

Además de ayudar con los hooks, métodos estáticos son una manera excelente de aislar tu código de WordPress. Te permiten manejar mucha de la funcionalidad de WordPress antemano. También este vuelva a la idea que tu clase debería tener toda la información que necesita internamente. No debería depender de información externa para funcionar.

namespace WPMemeShortcode;
 
class Opciones
{
    /**
     * Cargar los opciones del plugin de WordPress.
     *
     * @return Opciones
     */
    public static function cargar()
    {
        $opciones = get_option('wp_meme_shortcode', array());
 
        return new self($opciones);
    }
 
    // ...
 
}

Para ser claro, deberías usar funciones WordPress si tiene sentido al interior de tu clase. WordPress tiene mucha funciones ayudantes que deberías hacer uso. Ese dijo, funciones como get_option no ayudan tu clase a hacer su trabajo. Es por eso que son buenas candidatas por extracción en un método estático.

¿Por qué esto es útil para ti?

Así esto es mucho para digerir. ¿Quízas tienes dolor de cabeza en este momento? Incluso podrías preguntarte si vale la pena. Así si llegaste tan lejos, te dejaré con algunas razones para cuidar sobre el principio de responsabilidad única.

Tu código es más claro

¿Alguna vez has mirado tu código (¡o peor el código de otra persona!) y te has preguntado?

  • ¿Dónde pongo esta función?
  • ¡Donde puse esta función!
  • ¡Dios mío! ¡Qué estaba pensando!

¡Sabes que tienes! Es por eso que, si cada clase tiene solo un trabajo, hace tu código más fácil de comprender y leer para ti y otros. En la mayoría de los casos, no necesitas preguntarte donde pusiste una función ni donde deberías ponerla. Esa decisión es casi hecha para ti.

Tu código es más fuerte

Por tener tus clases hacen sola una cosa, estás haciendo todo tu código más robusto. Por ejemplo, déjanos decir que cada clase está en cargo de obtener sus propias opciones. Un día, me das cuenta de que tienes demasiadas opciones individuales y decides usar un vector.

Tendrías entonces buscar todo el código en tu plugin y hacer los cambios ahí. Cada cambio individual podría crear un bug. Tener una clase sola en carga de los opciones significa que solo necesitas hacer el cambio en un lugar.

Tu código es más fácil de probar

Prueba unitaria no es una cosa que muchos desarrolladores WordPress hacen. (¡Por el momento!) Eso dijo, cuando tus clases especializan en una cosa, son fáciles de probar y modelar. Este hace probar mucho menos complicado si alguna vez decides hacerlo. Usar un método estático como constructor para aislar algún de la funcionalidad WordPress también ayuda con eso.

Deja un comentario

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