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:
- Se busca
controllers/Index.php - Se instancia la clase
Index. - Se ejecuta la función
_init()deIndex. - Se ejecuta la función
_main()deIndex.
Y si se visita
http://www.example.com/clientes
Ocurrirá lo siguiente:
- Se busca
controllers/Clientes.php - Se instancia la clase
Clientes. - Se ejecuta la función
_init()deClientes. - Se ejecuta la función
_main()deClientes.
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 prefijoCtrl_.Ctrl_clientes. No comienza por mayúsculas tras el prefijoCtrl_.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 (ejemplohttps://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
Indexno 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()devuelvehttps://www.example.com/clientes/. - La función
_urlPath()devuelvehttps://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.