Controladores


1Presentación

Los controladores son objetos PHP que deben cumplir 4 criterios:

  • Heredar del objeto \Temma\Web\Controller.
  • Tener un nombre en StudlyCase (primera letra en mayúscula, el resto en minúscula, cada palabra empezando con mayúscula, sin guion bajo): ArticleViewer, ApplicationCms, MobileAppLibrary.
  • Estar contenido en un archivo cuyo nombre sea exactamente el del objeto, con el sufijo ".php".
  • Ser accesible por el autoloader:
    • Si el objeto no está en un namespace, el archivo debe colocarse en el directorio controllers/ del proyecto.
    • Si el objeto está en un namespace, el archivo debe colocarse:
      • en una estructura de árbol dentro del directorio controllers/ o del directorio lib/ del proyecto,
      • o en una estructura de árbol colocada dentro de una ruta declarada mediante la directiva de configuración includePaths,

Las acciones son los métodos públicos del controlador, y sus nombres deben empezar con una letra minúscula.


2Ejemplo

/** Controlador de usuarios. */
class User extends \Temma\Web\Controller {
    /**
     * Acción raíz.
     * Se llama para la URL "/user".
     */
    public function __invoke() {
        // ...
    }
    /**
     * Acción de visualización.
     * Se llama para URLs como "/user/show/123".
     * @param  int  $id  Identificador del usuario que debe mostrarse.
     */
    public function show(int $id) {
        // ...
    }
    /**
     * Acción por defecto.
     * Se llama cuando la acción solicitada no existe.
     * @param  string $name    Nombre de la acción solicitada.
     * @param  array  $params  Lista de posibles parámetros.
     */
    public function __call($name, $params) {
        // ...
    }
}
/** Controlador de artículos. */
class Article extends \Temma\Web\Controller {
    /** Inicialización, se llama antes de la acción. */
    public function __wakeup() {
        // ...
    }
    /** Finalización, se llama después de la acción. */
    public function __sleep() {
        // ...
    }
    /**
     * Acción proxy, se llama siempre.
     * @param  string $name    Nombre de la acción llamada.
     * @param  array  $params  Lista de parámetros proporcionados.
     */
    public function __proxy($name, $params) {
        // ...
    }
}

3Raíz, proxy y por defecto

Es posible definir un controlador raíz, un controlador proxy y un controlador por defecto (ver configuración):

  • El controlador raíz (directiva rootController) se llama cuando no se ha solicitado explícitamente ningún controlador. Se usa para definir el controlador que gestiona la página de inicio del sitio.
  • El controlador proxy (directiva proxyController) se llama siempre, incluso si el controlador llamado existe. Se usa para eludir completamente el framework, para crear tu propia gestión de todas las URLs.
  • El controlador por defecto (directiva defaultController) se llama cuando el controlador solicitado no existe. Permite definir un controlador que gestione URLs que no se conocen de antemano.

Un controlador también puede contener una acción raíz, una acción proxy y una acción por defecto:

  • La acción raíz se define con el método mágico __invoke().
    Se usa cuando no se ha solicitado explícitamente ninguna acción (por ejemplo con /myController).
  • La acción proxy se define con el método mágico __proxy().
    Si existe, se llama siempre, incluso si la acción solicitada existe.
  • La acción por defecto se define con el método mágico __call().
    Ofrece a un controlador la posibilidad de gestionar URLs que no se conocen de antemano, además de sus acciones especificadas.

Por ejemplo, si el objeto User contiene los métodos __invoke(), show() y __call(), se puede llamar con las siguientes URLs:

  • http://mysite.com/user => usa la acción raíz (el método __invoke())
  • http://mysite.com/user/show => usa la acción show (el método show())
  • http://mysite.com/user/list => usa la acción por defecto (el método __call)
  • http://mysite.com/user/add => usa la acción por defecto (el método __call)

Si este objeto contuviera un método __proxy(), se llamaría siempre.

El método __invoke() no recibe ningún parámetro.
Los métodos __proxy() y __call() esperan dos parámetros:

  1. Una cadena que contiene el nombre de la acción solicitada.
  2. Un array que contiene la lista de parámetros que deberían haberse pasado a la acción.

En cuanto a la carga de la plantilla: si no se define explícitamente ninguna plantilla (mediante el método _template() o el atributo Template), Temma usa un archivo cuyo nombre coincide con el nombre de la acción, en un subdirectorio con el nombre del controlador.

  • Para la acción raíz __invoke(), la plantilla cargada será templates/Controller/__invoke.tpl.
  • Para la acción por defecto __call(), la plantilla coincidirá con el nombre de la acción solicitada en la URL (no __call.tpl): por ejemplo, la URL /user/list gestionada por __call() usará la plantilla templates/User/list.tpl.
  • Para la acción proxy __proxy(), la plantilla usada también coincidirá con el nombre de la acción solicitada en la URL (no __proxy.tpl).

El uso de los métodos mágicos de PHP de esta manera no es un problema y no genera ambigüedad. Esto proporciona una separación clara entre las acciones "normales" y las acciones raíz/proxy/por defecto.


4Inicialización

Un controlador puede contener el método mágico __wakeup() (que no recibe parámetros).

Se llamará automáticamente antes de que se llame a la acción, lo que puede usarse para inicializar el controlador. Así es posible inicializar los atributos privados del controlador, instanciando objetos que pueden usarse en todas las acciones.

Este método puede no devolver nada, en cuyo caso la ejecución continúa con normalidad. También puede devolver los mismos valores que los métodos de acción (ver Flujo de ejecución), lo que puede modificar la ejecución de la acción, los post-plugins y la vista.


5Finalización

Un controlador puede contener el método mágico __sleep() (que no recibe parámetros).

Se llamará automáticamente después de que se haya llamado a la acción. Puede ser útil para liberar memoria inicializada por el controlador.

Este método puede no devolver nada, en cuyo caso la ejecución continúa con normalidad. También puede devolver los mismos valores que los métodos de acción (ver Flujo de ejecución), y así modificar la ejecución de los post-plugins y de la vista.


6Gestión de las variables de plantilla

Las variables de plantilla son valores definidos por los plugins y/o las acciones. Se pueden usar directamente en las plantillas, pero también son la forma principal de transferir datos entre los pre-plugins, el controlador y los post-plugins.

Para crear o modificar una variable de plantilla:

$this['myVariable'] = 'my value';

Para leer una variable de plantilla:

$value = $this['myVariable'];

Para saber si una variable de plantilla existe, o si está vacía:

// ¿está definida la variable?
if (isset($this['myVariable']))
    doSomething();

// ¿está vacía la variable?
if (empty($this['myOtherVariable']))
    doSomethingElse();

// variable no definida: se usa un valor por defecto
$var = $this['myVariable'] ?? 'defaultValue';

// variable no definida o vacía: valor por defecto
$var = $this['myVariable'] ?: 'defaultValue';

Para destruir una variable de plantilla:

unset($this['myVariable']);

Importante: las variables de plantilla cuyo nombre empieza con un guion bajo ("_") son "privadas" para los plugins y para el controlador; no están disponibles en las plantillas.


7Variables de plantilla definidas por Temma

El framework define varias variables de plantilla que están disponibles en los controladores (y en los plugins):

  • $URL: URL solicitada, a partir de la barra raíz.
  • $CONTROLLER: Nombre del controlador solicitado.
  • $ACTION: Nombre de la acción solicitada.

Además de estas variables, todos los datos configurados en la sección autoimport de la configuración se importan automáticamente como variables de plantilla.


8Métodos utilizables en los controladores

Los controladores ofrecen métodos específicos que pueden usarse en el código de la acción.

$this->_template(string)
Si usamos la vista por defecto (\Temma\Views\Smarty), este método redefine la ruta de la plantilla a usar. Si este método no se usa para especificar una plantilla, Temma intentará usar un archivo cuyo nombre sea el de la acción (con el sufijo ".tpl"), colocado en un directorio cuyo nombre sea el del controlador.
Siempre devuelve el valor self::EXEC_FORWARD (ver la documentación sobre el flujo de ejecución).

También es posible definir la plantilla a usar con el atributo Template.

$this->_redirect(?string $url=null, bool $referer=false)
Si quieres hacer una redirección (y por tanto no usar una vista), este método define la URL de redirección. Si $referer se pone a true, la cabecera HTTP REFERER se usa como URL de redirección, con $url como valor de respaldo si la cabecera falta o está vacía.
Siempre devuelve el valor self::EXEC_HALT (ver la documentación sobre el flujo de ejecución).

$this->_redirect301(?string $url=null, bool $referer=false)
Igual que el método anterior, pero realiza una redirección 301 (permanente) y no 302 (temporal). Si $referer se pone a true, la cabecera HTTP REFERER se usa como URL de redirección, con $url como valor de respaldo si la cabecera falta o está vacía.
Siempre devuelve el valor self::EXEC_HALT (ver la documentación sobre el flujo de ejecución).

$this->_httpError(int)
Si se encuentra un error que debe gestionarse de forma genérica, este método permite especificar el código de error HTTP (4XX, 5XX).
Siempre devuelve el valor self::EXEC_HALT (ver la documentación sobre el flujo de ejecución).

$this->_httpCode(int)
Permite especificar el código de respuesta HTTP, igual que el método _httpError(), salvo que no se genera ningún error y el procesamiento no se interrumpe.
Siempre devuelve el valor self::EXEC_HALT (ver la documentación sobre el flujo de ejecución).

$this->_view(string)
Se usa para definir la vista que se utilizará. Por defecto, es \Temma\Views\Smarty.
Siempre devuelve el valor self::EXEC_FORWARD (ver la documentación sobre el flujo de ejecución).

También es posible definir la vista a usar con el atributo View.

$this->_header(string)
Se usa para definir una cabecera HTTP que enviará la vista.

$this->_getHttpError()
Devuelve el código de error HTTP que se definió con _httpError(), o null si no se definió ninguno.

$this->_getHttpCode()
Devuelve el código de respuesta HTTP que se definió con _httpCode(), o null si no se definió ninguno.

$this->_templatePrefix(string)
Permite definir un prefijo para la ruta de carga de los archivos de plantilla.
Devuelve la instancia del objeto actual.

También es posible definir la plantilla a usar con el atributo Template, pasándole el parámetro prefix.

$this->_subProcess(string, string, array)
Se usa para ejecutar una acción de otro controlador. Ver más abajo.
Devuelve el estado de ejecución del subcontrolador.


9Atributos de los controladores

$this->_loader
Objeto de inyección de dependencias, que permite recuperar otros objetos.
Más información en la documentación de inyección de dependencias.

$this->_session
$this->_loader->session
A menos que la configuración haya previsto desactivar la gestión de sesiones, este atributo contiene una instancia de \Temma\Base\Session.
Más información en la documentación de sesiones.

$this->_config
$this->_loader->config
Este atributo contiene una instancia de \Temma\Web\Config. El controlador puede así acceder (solo lectura) tanto a los parámetros de la aplicación Temma como a los parámetros extendidos.
Más información en la documentación de configuración y en la documentación del objeto Config.

$this->_request
$this->_loader->request
Este atributo contiene una instancia de tipo \Temma\Web\Request. Esto permite recuperar, e incluso modificar, los distintos componentes de la petición.
Más información en la documentación del objeto Request.

$this->_response
$this->_loader->response
Este atributo contiene una instancia de \Temma\Web\Response. Esto permite controlar algunos parámetros de la respuesta.
Más información en la documentación del objeto Response.

$this->_dao
Si el controlador lo solicita, Temma instanciará automáticamente un objeto DAO para facilitar la comunicación con la base de datos.
Más información en la documentación del modelo.

$this->_temmaAutoDao
Este atributo es diferente de los demás. No lo establece el framework. Depende de ti usarlo al crear el controlador, para pedirle a Temma que cree automáticamente el objeto DAO que facilita el acceso a la base de datos.
Más información en la documentación del modelo.


10Acceso a las fuentes de datos

Un controlador puede acceder a las fuentes de datos de dos maneras diferentes: mediante el componente de inyección de dependencias, o directamente como si fueran atributos del controlador.

Imagina que el archivo etc/temma.php define fuentes de datos llamadas db (base de datos MySQL), ndb (base de datos Redis) y cache (servidor Memcached).

Acceso como atributos:

$db = $this->db;
$ndb = $this->ndb;
$cache = $this->cache;

Acceso mediante el componente de inyección de dependencias:

// acceso orientado a objetos
$db = $this->_loader->dataSources->db;
$ndb = $this->_loader->dataSources->ndb;
$cache = $this->_loader->dataSources->cache;

// acceso como array
$db = $this->_loader->dataSources['db'];
$ndb = $this->_loader->dataSources['ndb'];
$cache = $this->_loader->dataSources['cache'];

11Subprocesamiento

En una acción, podemos llamar muy fácilmente a la ejecución de otra acción del mismo controlador:

class Article extends \Temma\Web\Controller {
    // no queremos mostrar la lista de artículos,
    // solo queremos mostrar el primer artículo
    public function list() {
        // llamamos a la acción show()
        $this->show(1);

        // obtenemos el artículo en la variable de plantilla
        // creada por la acción show()
        $article = $this['article'];

        // creamos una nueva variable de plantilla
        // que contiene una lista de un único elemento
        $this['articles'] = [$article];
    }

    // acción usada para mostrar un artículo
    public function show(int $id) {
        $this['article'] = $this->_loader->ArticleDao->get($id);
    }
}

Un controlador también puede transmitir la ejecución de la petición actual a otro controlador. Para ello, basta con usar el método _subProcess(). Incluso es posible modificar la petición de antemano, para adaptarla al subcontrolador, o pasar directamente la lista de parámetros a usar como tercer argumento ($parameters), en lugar de los de la petición actual:

class HomepageController extends \Temma\Web\Controller {
    public function index() {
        // modificamos el primer parámetro de la acción
        $this->_loader->request->setParam(0, 'display');

        // llamamos a la acción "publish" del controlador "User"
        $this->_subProcess('User', 'publish');

        // podemos recuperar una variable de plantilla
        // definida por el subcontrolador...
        $name = $this['name'];

        // ...y luego sobrescribir este valor condicionalmente
        if (strlen($name) < 3)
            $this['name'] = 'nombre por defecto';
    }
}

En este ejemplo, el controlador que gestiona la página de inicio del sitio realiza una subpetición equivalente a una conexión a la URL /user/publish/display y luego realiza un procesamiento condicional sobre los datos generados.