Contrôleurs


1Présentation

Les contrôleurs sont des objets PHP qui doivent satisfaire 4 critères :

  • Hériter de l'objet \Temma\Web\Controller.
  • Être nommé en StudlyCase (première lettre en majuscule, le reste en minuscule, chaque mot commençant par une majsucule, sans underscore) : ArticleViewer, ApplicationCms, MobileAppLibrary.
  • Être contenu dans un fichier dont le nom est exactement celui de l'objet, suffixé par «.php»
  • Être accessible par l'autoloader :
    • Si l'objet n'est pas dans un namespace, le fichier doit être placé dans le répertoire controllers/ du projet.
    • Si l'objet est dans un namespace, le fichier doit être placé :
      • soit dans une arborescence à l'intérieur du répertoire controllers/ ou du répertoire lib/ du projet,
      • soit dans une arborescence placée à l'intérieur d'un chemin déclaré par la directive de configuration includePaths,
      • soit dans un emplacement déclaré par la directive de configuration namespacePaths.

Les actions sont les méthodes publiques du contrôleur, et leurs noms doivent commencer par une minuscule.


2Exemple

/** Contrôleur des utilisateurs. */
class User extends \Temma\Web\Controller {
    /**
     * Action racine.
     * Appelée pour l'URL "/user".
     */
    public function __invoke() {
        // ...
    }
    /**
     * Action d'affichage.
     * Appelée pour l'URL "/user/show/123".
     * @param  int  $id  Identifiant de l'utilisateur à afficher.
     */
    public function show(int $id) {
        // ...
    }
    /**
     * Action par défaut.
     * Appelée lorsque que l'action demandée n'existe pas.
     * @param  int    $name    Nom de l'action demandée.
     * @param  array  $params  Liste des paramètres éventuels.
     */
    public function __call($name, $params) {
        // ...
    }
}
/** Contrôleur des articles. */
class Article extends \Temma\Web\Controller {
    /** Initialisation, appelée avant l'action. */
    public function __wakeup() {
        // ...
    }
    /** Finalisation, appelée après l'action. */
    public function __sleep() {
        // ...
    }
    /**
     * Action proxy, reçoit tous les appels.
     * @param  string $name    Nom de l'action qui était appelée.
     * @param  array  $params  Liste des paramètres fournis.
     */
    public function __proxy($name, $params) {
        // ...
    }
}

3Racine, proxy et défaut

Il est possible de définir un contrôleur racine, un contrôleur proxy et un contrôleur par défaut (cf. configuration) :

  • Le contrôleur racine (directive rootController) est appelé lorsque aucun contrôleur n'a été explicitement demandé. Il sert à définir le contrôleur qui gère la page d'accueil du site.
  • Le contrôleur proxy (directive proxyController) est systématiquement appelé, même si le contrôleur appelé existe. Il sert à contourner complètement le framework, pour créer votre propre gestion de toutes les URL.
  • Le contrôleur par défaut (directive defaultController) est appelé lorsque le contrôleur demandé n'existe pas. Il permet de définir un contrôleur qui gère des URL qui ne sont pas connues d'avance.

Un contrôleur peut aussi contenir une action racine, une action proxy et une action par défaut :

  • L'action racine se définit avec la méthode magique __invoke().
    Elle est utilisée quand aucune action n'a été explicitement demandée (par exemple l'URL "/monControleur").
  • L'action proxy se définit avec la méthode magique __proxy().
    Si elle existe, elle est systématiquement appelée, même si l'action demandée existe.
  • L'action par défaut se définit en utilisant la méthode magique __call().
    Elle offre la possibilité à un contrôleur de gérer des URL qui ne sont pas connues à l'avance, en plus de ses actions spécifiées.

Par exemple, si l'objet User contient les méthodes __invoke(), show() et __call(). Il pourra être appelé avec les URL suivantes :

  • http://monsite.com/user => utilise l'action racine (méthode __invoke())
  • http://monsite.com/user/show => utilise l'action show (méthode show())
  • http://monsite.com/user/list => utilise l'action par défaut (méthode __call())
  • http://monsite.com/user/add => utilise l'action par défaut (méthode __call())

Si cet objet contenait une méthode __proxy(), celle-ci serait appelée dans tous les cas.

La méthode __invoke() ne prend aucun paramètre.
Les méthodes __proxy() et __call() attendent deux paramètres :

  1. Un chaîne de caractère contenant le nom de l'action qui était demandée.
  2. Un tableau contenant la liste des paramètres qui auraient dû être transmis à l'action.

Le détournement des méthodes magiques de PHP ne pose pas de problème et n'entraîne pas d'ambiguïté. Cela offre un découpage clair entre les action "normales" et les actions racine/proxy/par défaut.


4Initialisation

Un contrôleur peut contenir la méthode magique __wakeup() (qui ne prend aucun paramètre).

Elle sera appelée automatiquement avant que l'action soit appelée, ce qui peut servir à initialiser le contrôleur. Il est ainsi possible d'initialiser des attributs privés du contrôleur, en instanciant des objets qui pourront être utilisés dans toutes les actions.

Cette méthode peut ne rien retourner, auquel cas l'exécution se poursuit normalement. Elle peut aussi retourner les mêmes valeurs que les méthodes d'action (cf. Flux d'exécution), ce qui peut modifier l'exécution de l'action, des post-plugins et de la vue.


5Finalisation

Un contrôleur peut contenir la méthode magique __sleep() (qui ne prend aucun paramètre).

Elle sera appelée automatiquement après que l'action ait été appelée, ce qui peut servir à libérer de la mémoire initialisée par le contrôleur.

Cette méthode peut ne rien retourner, auquel cas l'exécution se poursuit normalement. Elle peut aussi retourner les mêmes valeurs que les méthodes d'action (cf. Flux d'exécution), ce qui peut modifier l'exécution des post-plugins et de la vue.


6Gestion des variables de template

Les variables de templates sont des valeurs qui sont définies par les plugins et/ou les actions. Elles sont utilisables directement dans les templates, mais c'est aussi le principal moyen de transférer des données entre les pré-plugins, le contrôleur et les post-plugins.

Pour créer ou modifier une variable de template :

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

Pour lire une variable de template :

$value = $this['myVariable'];

Pour savoir si une variable de template existe, ou si elle est vide :

// est-ce que la variable est définie ?
if (isset($this['myVariable']))
    doSomething();

// est-ce que la variable est vide ?
if (empty($this['myOtherVariable']))
    doSomethingElse();

// variable non définie : utilisation d'une valeur par défaut
$var = $this['myVariable'] ?? 'defaultValue';

// variable non définie ou vide : valeur par défaut
$var = $this['myVariable'] ?: 'defaultValue';

Pour détruire une variable de template :

unset($this['myVariable']);

À savoir : les variables de templates dont le nom commence par un underscore ("_") sont “privées” aux plugins et au contrôleur ; elles ne sont pas disponibles dans les templates.


7Variables de template définies par Temma

Le framework définit plusieurs variables de template qui sont disponibles dans les contrôleurs (et dans les plugins) :

  • $URL : URL demandée, à partir du slash racine.
  • $CONTROLLER : Nom du contrôleur demandé.
  • $ACTION : Nom de l'action demandée.

En plus de ces variables, toutes les données configurées dans la section autoimport de la configuration sont automatiquement importées en tant que variables de template.


8Méthodes accessibles dans les contrôleurs

Les contrôleurs proposent des méthodes spécifiques utilisables dans le code des actions.

$this->_template(string)
Si on utilise la vue par défaut (\Temma\Views\Smarty), cette méthode redéfinit le chemin vers le template à utiliser. Si cette méthode n'est pas utilisée pour spécifier un template, Temma va chercher à utiliser un fichier dont le nom est celui de l'action (avec le suffixe ".tpl"), placé dans un répertoire dont le nom est celui du contrôleur.
Retourne l'instance de l'objet courant.

Il est aussi possible de définir le template à utiliser avec l'attribut Template.

$this->_redirect(string)
Si on souhaite faire une redirection (et donc ne pas utiliser une vue), cette méthode définit l'URL de redirection.
Retourne toujours la valeur self::EXEC_HALT (voir la documentation sur les flux).

$this->_redirect301(string)
Idem à la méthode précédente, mais effectue une redirection 301 (permanente) et non pas 302 (temporaire).
Retourne toujours la valeur self::EXEC_HALT (voir la documentation sur les flux).

$this->_httpError(int)
Si on rencontre une erreur qui doit être traitée de manière générique, cette méthode permet de spécifier le code d'erreur HTTP (4XX, 5XX).
Retourne toujours la valeur self::EXEC_HALT (voir la documentation sur les flux).

$this->_httpCode(int)
Permet de spécifier le code de réponse HTTP, comme la méthode httpError(), sauf qu'aucune erreur n'est levée et le traitement n'est pas interrompu.
Retourne toujours la valeur self::EXEC_HALT (voir la documentation sur les flux).

$this->_view(string)
Sert à définir la vue qui sera utilisée. Par défaut, il s'agit de \Temma\Views\Smarty.
Retourne l'instance de l'objet courant.

Il est aussi possible de définir la vue à utiliser avec l'attribut View.

$this->_header(string)
Sert à définir un en-tête HTTP qui sera émis par la vue.

$this->_getHttpError()
Retourne le code d'erreur HTTP qui a été défini avec httpError(), ou null si aucun n'a été défini.

$this->_getHttpCode()
Retourne le code de réponse HTTP qui a été défini avec httpCode(), ou null si aucun n'a été défini.

$this->_templatePrefix(string)
Permet de définir un préfixe au chemin de chargement des fichiers de template.
Retourne l'instance de l'objet courant.

Il est aussi possible de définir le préfixe de template avec l'attribut Template, en lui passant le paramètre prefix.

$this->_subProcess(string, string)
Permet d'exécuter une action d'un autre contrôleur. Voir plus bas.
Retourne le statut d'exécution du sous-contrôleur.


9Attributs des contrôleurs

$this->_loader
Objet d'injection de dépendances, qui permet de récupérer d'autres objets.
Plus d'information sur la documentation de l'injection de dépendances.

$this->_session
$this->_loader->session
À moins que la configuration ait prévu de désactiver la gestion des sessions, cet attribut contient une instance de \Temma\Base\Session.
Plus d'information sur la documentation des sessions.

$this->_config
$this->_loader->config
Cet attribut contient une instance de \Temma\Web\Config. Le contrôleur peut ainsi accéder (en lecture seule) aussi bien aux paramètres de l'application Temma qu'aux paramètres étendus.
Plus d'information sur la documentation de la configuration et la documentation de l'objet Config.

$this->_request
$this->_loader->request
Cet attribut contient une instance de type \Temma\Web\Request. Cela permet de récupérer, voire modifier, les différentes composantes de la requête.
Plus d'information sur la documentation de l'objet Request.

$this->_response
$this->_loader->response
Cet attribut contient une instance de \Temma\Web\Response. Cela permet de contrôler certains paramètres de la réponse.
Plus d'informations sur la documentation de l'objet Response.

$this->_dao
Si le contrôleur le demande, Temma va automatiquement instancier un objet DAO pour faciliter la communication avec la base de données.
Plus d'informations sur la documentation du modèle.

$this->_temmaAutoDao
Cet attribut est différent des autres. Il n'est pas positionné par le framework. C'est à vous de l'utiliser à la création du contrôleur, pour demander à Temma de créer automatiquement l'objet DAO qui facilite l'accès à la base de données.
Plus d'informations sur la documentation du modèle.


10Accès aux sources de données

Un contrôleur peut accéder aux sources de données de deux manières différentes : soit en passant par le composant d'injection de dépendances, soit directement comme s'il s'agissait d'attributs du contrôleur.

Imaginons que le fichier etc/temma.php définisse des sources de données nommées db (base MySQL), ndb (base Redis) et cache (serveur Memcached).

Accès comme attributs :

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

Accès via le composant d'injection de dépendance :

// accès orienté objet
$db = $this->_loader->dataSources->db;
$ndb = $this->_loader->dataSources->ndb;
$cache = $this->_loader->dataSources->cache;

// accès de type tableau
$db = $this->_loader->dataSources['db'];
$ndb = $this->_loader->dataSources['ndb'];
$cache = $this->_loader->dataSources['cache'];

11Sous-traitement

Dans une action, on peut très facilement appeler l'exécution d'une autre action du même contrôleur :

class Article extends \Temma\Web\Controller {
    // on ne veut pas afficher la liste des articles,
    // on veut juste afficher le premier article
    public function liste() {
        // on appelle l'action voir()
        $this->voir(1);

        // on récupère l'article dans la variable de template
        // créée par l'action voir()
        $article = $this['article'];

        // on crée une nouvelle variable de template
        // contenant une liste d'un seul article
        $this['articles'] = [$article];
    }

    // action utilisée pour afficher un article
    public function voir(int $id) {
        $this['article'] = $this->_loader->ArticleDao->get($id);
    }
}

Un contrôleur peut aussi transmettre l'exécution de la requête courante à un autre contrôleur. Pour cela, il suffit d'utiliser la méthode subProcess(). Il est même possible de modifier la requête à l'avance, pour l'adapter au sous-contrôleur :

class HomepageController extends \Temma\Web\Controller {
    public function index() {
        // on modifie le premier paramètre de l'action
        $this->_loader->request->setParam(0, 'display');

        // on appelle l'action "publish" du contrôleur "User"
        $this->subProcess('User', 'publish');

        // on peut récupérer une variable de template
        // définie par le sous-contrôleur...
        $name = $this['name'];

        // ...puis écraser cette valeur de manière conditionnelle
        if (strlen($name) < 3)
            $this['name'] = 'nom par défaut';
    }
}

Dans cet exemple, le contrôleur qui gère la page d'accueil du site effectue une sous-requête équivalente à une connexion à l'URL /user/publish/display puis effectue un traitement conditionnel sur les données générées.