Controladores

ZWF implementa el patrón llamado «Controlador Frontal» (Front Controller). En ZWF, todas las peticiones son canalizadas mediante el fichero /index.php.

Por otro lado se fomenta el uso del patrón MVC (Model-View-Controller) en su aplicación más sencilla, que es una implementación web. Para ello facilita la creación y separación de:

  • El modelo de datos en un conjunto separado de clases.
  • Vistas gestionadas y encapsuladas.
  • Biblioteca de clases adicionales en tierra de nadie.
  • Controladores que canalizan el flujo, aglutinan y comunican todo lo anterior.

Sin embargo, todo esto es opcional, salvo la creación de, al menos, un controlador. Un controlador es una clase derivada de la clase \zfx\Controller (proporcionada por ZWF y que se encuentra en el fichero base/zfx/Controller.php) y que se almacena en el directorio controllers/.

Autocarga de clases

En una aplicación que use ZWF no es necesario hacer include(...) de ficheros .php para cargar clases.

Al usar una clase definida por ZWF (que se encuentran en el directorio base/zfx/), así como los modelos y clases de biblioteca ( ubicadas en models/ y lib/ respectivamente), se busca el fichero cuyo nombre coincide con el nombre de la clase (y extensión .php) y se hace include() de dicho fichero.

La excepción a esto son los controladores. El directorio controllers/ no está incluído en la autocarga de clases y para incluir un fichero desde este directorio sí es necesario usar include() o similar.

¿Para qué sirve el controlador?

Supondremos que la aplicación va a tener la siguiente dirección raíz:

http://www.example.com

Cuando visitamos la dirección:

http://www.example.com/clientes

El framework va a ser buscar el controlador llamado Clientes que esperará encontrar dentro del fichero controllers/Clientes.php. Si no lo encuentra, mostrará un mensaje de error. Pero si lo encuentra, cargará el fichero y creará una instancia de esa clase.

Si se visita la dirección raíz, o sea:

http://www.example.com

Lo que se buscará es el controlador predeterminado, que por defecto se llama Index.

De forma predeterminada se ha establecido que, una vez que se instancia la clase controlador, si en la clase controlador se encuentran las funciones públicas _init() y _main(), se ejecutarán. Por lo tanto, si se visita

http://www.example.com

Ocurrirá lo siguiente:

  1. Se busca controllers/Index.php
  2. Se instancia la clase Index.
  3. Se ejecuta la función _init() de Index.
  4. Se ejecuta la función _main() de Index.

Y si se visita

http://www.example.com/clientes

Ocurrirá lo siguiente:

  1. Se busca controllers/Clientes.php
  2. Se instancia la clase Clientes.
  3. Se ejecuta la función _init() de Clientes.
  4. Se ejecuta la función _main() de Clientes.

Proporcionar las funciones _init() y _main() es una forma de disponer de funciones que se sabe que siempre se van a ejecutar. De este modo podemos preservar el constructor de la clase para su inicialización y los mecanismos de herencia en caso necesario.

Reglas de creación de un controlador

Como se ha dicho, al menos hace falta un controlador en la aplicación. Salvo que se haya configurado otra cosa, su nombre debe ser Index y su implementación debería ser similar a

class Ctrl_Index extends \zfx\Controller
{
    //...
}

El nombre de los ficheros deben coincidir con el de la clase exactamente. Por ejemplo la clase Clientes debe ubicarse en el fichero Clientes.php y la clase Ctrl_ListaProductos debe ir en el fichero Ctrl_ListaProductos.php.

Los controladores derivan de la clase Controller.

Los controladores deben nombrarse según las siguientes reglas:

  • Comenzar con un prefijo. De forma predeterminada es Ctrl_.
  • A continuación, una letra mayúscula (ejemplo: Ctrl_Index).
  • Seguir con letras o números, pero ningún otro símbolo (especialmente el signo de subrayado: no se puede usar). Ejemplo: Ctrl_I18n.
  • Admite mayúsculas en el interior, lo que permite usar la «notación de camello» para nombres compuestos (ejemplo: Ctrl_ExportadorProductos).

Nombres de controladores inválidos:

  • Index. No comienza por el prefijo Ctrl_.
  • Ctrl_clientes. No comienza por mayúsculas tras el prefijo Ctrl_.
  • Ctrl_Lista_productos. Contiene signo de subrayado.

La relación entre URL y el nombre del controlador

Básicamente la forma de acceder a la aplicación que usa ZWF es la siguiente:

rooturl/controlador/segmento1/segmento2/.../segmentoN

Siendo:

  • rooturl: la dirección raíz (ejemplo https://www.example.com)
  • controlador: el nombre en minúsculas del controlador;
  • segmento1, segmento2,... Datos que se le pasan a la aplicación.

En la URL, el nombre del controlador siempre es en minúsculas. Las mayúsculas están prohibidas.

Si el controlador es un nombre en notación de camello, en la URL cada palabra debe separarse por un guión.

Ejemplo: la dirección

http://www.example.com/lista-productos

Invocará al controlador Ctrl_ListaProductos.

Lo que sigue al controlador:

segmento1/segmento2/.../segmentoN

Se denominan segmentos. Son datos que se le pasan al controlador. Más adelante se verá cómo leerlos y usarlos.

Por lo tanto, y en resumen:

  • Después de la URL raíz va el nombre del controlador. No simboliza ningún subdirectorio.
  • Después del nombre del controlador siguen los segmentos. Tampoco simbolizan subdirectorios.
  • Si el controlador es Index, no es necesario escribirlo, salvo que se configure lo contrario.

Hay dos consecuencias.

  • Consecuencia 1. Como el directorio res/ es accesible desde el exterior (pues se espera que contenga las imágenes, CSS, javascript y demás), no puede existir un controlador con el nombre de clase Ctrl_Res.
  • Consecuencia 2. El controlador Index no puede recibir datos (segmentos) porque no hay forma de distinguir si el primer segmento es en realidad un nombre de controlador:

Ejemplo:

http://www.example.com/dato1/dato2/dato3

dato1 ¿Es un controlador?

Pero sí puede recibir datos si se especifica su nombre explícitamente:

http://www.example.com/index/dato1/dato2/dato3

¿Por qué toda esta rigidez?

En realidad esta rigidez no es tal, pues se puede cambiar el comportamiento de ZWF; sin embargo ésta es la configuración predeterminada.

Lo que se persigue es facilitar el uso de URL fácilmente escribibles, recordables y que no induzcan a error; que funcionen bien con robots, buscadores e indizadores; que no permitan la introducción de datos que provoquen problemas de seguridad, inyecciones o errores del sistema. Ejemplos:

https://www.mitienda.com/linea-marron/televisores/philips
https://www.miapp.com/clientes/editar/id/3209
https://www.miapp.com/listados/productos-y-servicios

Son más fáciles de entender y de indizar que

https://www.mitienda.com/listar?cat=34&subcat=4&brand=PHILIPS
https://www.miapp.com/CRUD/action_edit?id=3209
https://www.miapp.com/list?type=2

Nota: El sistema permite el uso de datos GET de forma transparente, aunque no se recomienda:

https://www.example.com/productos?id=32

Prestaciones comunes de los controladores

Un controlador tiene funciones para recuperar datos de la URL o devolver su propia URL, entre otras muchas. En las siguientes secciones se usará $this-> para enfatizar que se usa desde el propio controlador.

Los segmentos del URL

En un controlador, invocar $this->_segment() devuelve un array con todos los segmentos que se pasaron en la URL.

Ejemplo: Si se visita

http://www.example.com/clientes/listar/32

Entonces llamar a $this->_segment() devuelve el array

(
    0 => 'listar',
    1 => 32
)

Si se llama a _segment() con un parámetro numérico, se obtiene solo ese segmento. Por ejemplo $this->_segment(0) en el ejemplo anterior devolvería la cadena listar.

Nótese cómo el segmento que corresponde al controlador no se incluye.

Ejecutar una función llamada como el primer segmento

En otros frameworks de forma predeterminada se ejecuta una función en el controlador que se llama como el primer segmento. Son las denominadas acciones.

En ZWF este comportamiento no ocurre, pero puede hacerse con la función $this->_autoexec().

Supongamos el controlador Clientes con estas dos funciones:

public function _main()
{
    $this->_autoexec()
}

public function listar()
{
    // ...Aquí hacemos algo
}

Si visitamos la URL:

http://www.example.com/clientes/listar/32

Ocurrirá que _autoexec() leerá el primer segmento (listar), buscará una función con el mismo nombre y la ejecutará. Después, _autoexec() retornará TRUE indicando de este modo que la encontró.

Si no hubiera una función pública en la clase con ese nombre, retornaría FALSE. Esto se puede aprovechar para hacer fallbacks:

public function _main()
{
    if (!$this->_autoexec())
    {
        // Mostrar el índice
    }
}

public function _capitulo1()
{
    // Mostrar el capítulo 1
}

La función _autoexec() se ocupa de obtener los segmentos y pasárselos a las funciones a las que intenta llamar como parámetros. Esto es un proceso automático.

Los segmentos están limitados a los caracteres [a-z0-9] y el guión. Sin embargo, no se puede usar guiones para los nombres de funciones PHP. Si se va a usar _autoexec() debe tenerse en cuenta.

Generar URLs

El controlador sabe dónde está:

  • La función _urlController() devuelve su propia URL, pero sin segmentos (o sea, solo devuelve hasta el controlador).
  • La función _urlPath() devuelve su propia URL con los segmentos que en ese momento se recibieron.

Ejemplo:

Sea el controlador Ctrl_Clientes y visitamos la dirección

https://www.example.com/clientes/borrar/56
  • La función _urlController() devuelve https://www.example.com/clientes/.
  • La función _urlPath() devuelve https://www.example.com/clientes/borrar/56.

Redirecciones

En cualquier momento podemos emitir una cabecera de redirección:

$this->_redirect("http://www.example.com/asuntos");

Esta cabecera solo funcionará antes de emitir alguna salida al navegador.

Controladores personalizados

Es muy habitual y conveniente crear un controlador abstracto que herede de los controladores básicos proporcionados por ZWF y usarlo como base para los controladores reales de nuestra aplicación.

Este controlador abstracto proporcionará todo el código e inicialización que necesiten nuestros controladores, como por ejemplo creación de menús, carga de plantillas, autenticación, etc.

Por claridad, se recomienda usar un prefijo (como por ejemplo Abs_) para nombrarlos. Al no usar el prefijo Ctrl_ el sistema no intentará servirlos, aunque lógicamente al ser controladores abstractos no pueden ser instanciados.