Sujet : Hoa_Controller, ça devient amusant
Hey
,
Comme vous le savez, Hoa_Controller est en refonte pour être plus léger. Non pas que l'ancien était mauvais mais il n'était pas assez souple à mon goût. Le nouveau est juste génial à mes yeux. Hoa s'emboîte parfaitement bien et montre toute sa force (tada !).
Hoa_Controller comporte les mêmes couches qu'avant mais présentées différement. On retrouve la requête, le routeur, le dispatcheur, la réponse et l'application. Fini les contrôleurs primaires et secondaires, c'était valable à l'époque, ça ne l'est plus aujourd'hui. PHP est plus rapide et les problématiques ne sont plus les mêmes. En effet, le but était d'éviter du calcul à PHP alors que maintenant, on veut éviter des accès disques (ma hantise du moment).
Que trouve-t-on alors dans Hoa_Controller ? Simplement : Hoa_Controller_Router, Hoa_Controller_Dispatcher et Hoa_Controller_Application. Ces trois éléments sont totalement dissociés et la configuration est carrément plus souple, ainsi que les possibilités que ça apporte.
Le dispatcheur a besoin d'un routeur et d'une vue pour fonctionner. Un routeur est Hoa_Controller_Router ou une fille. Une vue est n'importe quoi qui implémente Hoa_View_Viewable (le seul fichier de Hoa_View d'ailleurs).
Qu'est-ce qu'une vue ? Quelque chose qui porte des données, qui est capable de faire un rendu et qui a un flux de sortie (de type Hoa_Stream_Interface_Out) (en général, le rendu est fait sur le flux de sortie
).
Qu'est-ce qu'un routeur ? Il prend un ensemble de 3-uplet (ou 4-uplet) qui forme des règles. Ça donne :
$router->addRule(pattern, controller, action[, extra]) pattern est une expression régulière dans laquelle on capture nommément des parties, comme ^foo/bar/(?<baz>[^\.]+)\.html$. Ici, quand on écrit foo/bar/the-cake-is-a-lie.html, baz vaudra the-cake-is-a-lie, tout bêtement. Ensuite, controller est le nom du contrôleur ou son instance. Et action est le nom de l'action, le nom d'une fonction ou une closure. Enfin, extra correspond à des données qu'on veut passer à notre action (méthode, fonction, closure etc.).
Exemple pour le couple controller/action :
'gordon'/'freeman' : va appeler la classe du contrôleur gordon (par défaut GordonController) et la méthode de l'action freeman (par défaut FreemanAction) ;
$gordon/'freeman' : même chose, sauf que la classe est $gordon ;
null/'freeman' : va appeler la fonction appelée freeman ;
null/function ( ) { } : va appeler la fonction anonyme.
Si on appelle un objet ou une classe, il faut qu'il étende Hoa_Controller_Application, qui fournit juste 4 attributes : $router, $dispatcher, $view, $data (les données de la vue). Tout ce qu'il faut pour s'amuser.
Si on appelle une fonction (anonyme ou pas), si l'argument $_this est présent, il vaudra Hoa_Controller_Application.
Si le routeur capture d'autres parties, elles seront distribuées sur les arguments du même nom de l'action. Un exemple : si notre règle capture act et ion (de cette façon : ^foo/bar/(?<act>.*)-(?<ion>.*)\.html$, alors les valeurs seront disponibles de cette façon :
class GordonController extends Hoa_Controller_Application {
public function FreemanAction ( $act, $ion ) {
var_dump($act, $ion);
}
} Bien sûr, si ion n'est pas capturée et qu'on a déclaré $ion = 'tada' en argument, alors tout ira bien. Bref, c'est naturel quoi, faut pas trop réfléchir, c'est une distribution bête et méchante.
Et dans le cas d'une fonction (anonyme ou pas) :
function f ( Hoa_Controller_Application $_this, $act, $ion ) {
} $this dans une méthode est identique à $_this dans une fonction. Ça, c'est le côté pratique.
Dois-je préciser que si on capture (?<controller>) et (?<action>) ça va changer le contrôleur et l'action ? Rien de magique ou de particulier, il faut regarder comment fonctionne les règles (par « composant »/parties) et on va trouver ça logique.
Donc on avait dit qu'on a un dispatcheur. De quoi a-t-il besoin ? D'un routeur et d'une vue. Le routeur, c'est fait, on passe à la vue.
Prenons un exemple qui nous intéresse : Hoa_Xyl. XYL a besoin d'un flux d'entrée pour lire un document XYL, un flux de sortie et un interpréteur (comment interpréter XYL ? en HTML5, en PDF, en texte … ?). Alors, on va écrire ceci :
$dispatcher = new Hoa_Controller_Dispatcher();
$dispatcher->dispatch(
new Hoa_Controller_Router(),
new Hoa_Xyl(
new Hoa_File_Read('hoa://Application/View/Main.xyl'),
new Hoa_Http_Response(),
new Hoa_Xyl_Interpreter_Html5()
)
);Ça peut paraître un brin compliqué comme ça, mais on découple au maximum et au final, c'est extrêmement modulaire.
Au final, dans notre action (classe, méthode, fonction, closure, peu importe !), on va taper sur $this ou $_this pour avoir les données et faire le rendu, un truc du genre :
$this->data->foobar = 'Hello world!';
$this->view->render(); Et c'est fini.
Notez que si on utilise XYL, on peut désormais ajouter des <?xyl-use href="…"?> dynamiquement avec addUse($href), ainsi :
$this->view->addUse('hoa://Application/View/Use1.xyl');
$this->view->render();et le tour est joué. Pareil avec addOverlay($href).
Et maintenant, on veut s'amuser !
Comment passer d'une action à une autre ? On va utiliser le routeur et le dispatcheur qu'on a sous la main ! Un petit exemple :
$this->dispatcher->dispatch(
$this->router->route('/new/path')
); Si on ne donne pas la vue en second paramètre de dispatch(), on va prendre la vue courante (ça va de soit …). On peut échanger de routeur, échanger de vue, on fait ce qu'on veut, c'est très très souple. Libre à vous d'imaginer ce que vous voulez
.
Le fait que XYL gère énormément de chose nous simplifie grandement la vie. Par exemple, les formulaires seront gérés par XYL.
Ensuite, petit truc amusant. La gestion de l'asynchrone par Hoa_Controller.
Vous savez que tout est paramétrable dans Hoa grâce à Hoa_Core_Parameterizable et le zFormat. Les paramètres de Hoa_Controller_Dispatcher sont les suivants (au format JSON) :
{
"keywords": {
"controller": "main",
"action": "main",
"method": null
},
"parameters": {
"synchronous.file" : "hoa://Application/Controller/(:controller:U:).php",
"synchronous.controller" : "(:controller:U:)Controller",
"synchronous.action" : "(:action:U:)Action",
"asynchronous.file" : "(:%synchronous.file:)",
"asynchronous.controller": "(:%synchronous.controller:)",
"asynchronous.action" : "(:%synchronous.action:)Async"
}
} Bien sûr, vous pouvez vous amuser comme vous voulez, mais par défaut, si vous appelez une action de manière asynchrone (via XHR par exemple), alors on va chercher la méthode suffixée de Async. Par exemple : FreemanActionAsync.
On peut aller plus loin avec le mot-clé method. Si on écrit :
"asynchronous.action": "(:action:U:)Action(:method:U:)Async"alors ça pourrait donner FreemanActionGetAsync ou FreemanActionPostAsync etc. On parle bien des méthodes HTTP.
Bref. J'oublie encore des choses mais c'est déjà assez pour ce soir
.
J'ai alors une question. Maintenant que le contrôleur et la vue sont bien foutus (d'après moi, votre avis ?), il me reste le modèle. Comment souhaitez-vous que le modèle soit géré ?