Scénario MVC + vs contrôleurs épais
Les frameworks PHP modernes (Symphony, Laravel, ci-après partout) montrent de manière convaincante que la mise en œuvre du modèle Model-View-Controller n'est pas si simple. Toutes les implémentations pour une raison quelconque sont sujettes aux Fat Controllers (contrôleurs de fat controllers
), condamnés par tous, et les développeurs, et les frameworks eux-mêmes.
Pourquoi en est-il ainsi? Et existe-t-il un moyen de gérer cela? Faisons les choses correctement.
Terminologie
- Modèle - modèle (mise en forme des données demandées)
- Vue - vue (modèle de décorateur de données)
- Contrôleur - contrôleur (coordinateur de la vue du modèle sur demande)
- Template - modèle de présentation
- Rendu - rendu (formation, conception de l'image de présentation)
- Renderer - renderer (shaper, concepteur de l'image de présentation)
Contrôleur épais
Voici un Fat Controller typique:
class UserController { public function actionUserHello($userId) {
Que voyons-nous? On voit de la vinaigrette! Dans le contrôleur, tout ce qui est possible est mélangé - à la fois le modèle et la présentation, et, en fait, le contrôleur lui-même!
Nous voyons les noms du modèle et du modèle étroitement câblés dans le contrôleur. Ce n'est pas un buzz. Nous voyons des manipulations avec les données du modèle dans le contrôleur - la formation d'un nom complet à partir du prénom et du nom. Et ce n'est pas un buzz.
Et encore une chose: nous ne voyons pas cet exemple explicitement, mais il est implicite. A savoir: il n'y a qu'une seule façon de rendre (formation d'image)! Un seul: selon le modèle du fichier php! Et si je veux un pdf? Et si je ne veux pas dans un fichier, mais dans une ligne php? J'avais des designs avec des designs élaborés sur des centaines de petits modèles. J'ai dû laisser échapper le moteur de rendu pour les modèles de chaînes moi-même. Je n'ai pas surchauffé bien sûr, mais la question est en principe.
Bref résumé:
Les cadres modernes ont des défauts communs dans la mise en œuvre de MVC pour tout le monde:
- Interprétation étroite de MVC-View (View) uniquement comme "View with a template in a PHP file" au lieu de "View with any renderer" .
- Interprétation étroite du modèle MVC uniquement en tant que «domaine de modèle de base de données» au lieu de «tout compilateur de données pour la présentation» .
- Ils provoquent l'utilisation des soi-disant "contrôleurs épais" contenant à la fois toute la logique: métier, présentation et interaction. Cela détruit complètement l'objectif principal de MVC - la répartition des responsabilités entre les composants de la triade.
Pour remédier à ces lacunes, il serait intéressant d'examiner de plus près les composants de MVC.
La vue est un moteur de rendu
Jetez un œil au premier inconvénient:
- Interprétation étroite de MVC-View (View) uniquement comme "View with a template in a PHP file" au lieu de "View with any renderer" .
Ici, tout est assez simple - la solution du problème est déjà indiquée dans la formulation même du problème. Nous devons juste dire que n'importe quel moteur de rendu peut utiliser la vue. Pour implémenter cela, ajoutez simplement la nouvelle propriété de renderer
à la classe View:
class View { public $template, $data, $renderer; public function __costruct($template, $data, $renderer = NULL) {} }
Nous avons donc défini une nouvelle propriété de renderer
pour la vue. Dans le cas le plus général, la valeur de cette propriété peut être n'importe quelle fonction callable
qui forme une image des données qui lui sont transférées à l'aide du modèle transmis.
La plupart des applications n'utilisent qu'un seul moteur de rendu, et même si elles en utilisent plusieurs, l'une d'entre elles est préférée. Par conséquent, l'argument de renderer
est défini comme facultatif, en supposant qu'il existe un rendu par défaut.
C'est simple? C'est simple. En fait pas si simple. Le fait est que la View
qui se trouve dans MVC n'est pas exactement la View
qui se trouve dans les frameworks. La View
qui se trouve dans le cadre ne peut pas vivre sans modèle. Mais la View
, qui dans MVC, pour une raison quelconque, ne sait rien de ces mêmes modèles. Pourquoi? Oui, car pour MVC View
, il s'agit de n'importe quel convertisseur de données de modèle en image , et pas seulement d'un moteur de modèle. Lorsque nous écrivons quelque chose comme ça dans le gestionnaire de requêtes:
$name = ' '; return "Hello, {$name}!";
ou même:
$return json_encode($name);
alors nous définissons vraiment la View
qui est dans MVC, sans toucher à aucune View
qui est dans les frameworks!
Mais maintenant, tout est vraiment simple: ces View
, qui dans les frameworks - c'est un sous-ensemble de ces View
, qui sont dans MVC. De plus, un sous-ensemble très étroit, à savoir, ce n'est qu'un moteur de modèle basé sur des fichiers PHP.
Résumé: c'est le
, c'est-à-dire tout décorateur d'une image de données est la View
qui se trouve dans MVC. Et ces View
, qui se trouvent dans les frameworks, ne sont qu'une sorte de
de
.
Modèle de domaine / Afficher le modèle (ViewModel / DomainModel)
Regardez maintenant le deuxième inconvénient:
- Interprétation étroite du modèle MVC uniquement en tant que «domaine de modèle de base de données» au lieu de «tout compilateur de données pour la présentation» .
Il est évident pour tout le monde que le modèle MVC est une chose complexe qui se compose d'autres pièces. La communauté s'engage à décomposer le modèle en deux composantes: un modèle de domaine (DomainModel) et un modèle de présentation (ViewModel).
Un modèle de domaine est ce qui est stocké dans des bases de données, c'est-à-dire données de modèle normalisées. Tapez «prénom» et «nom» dans différents champs. Les cadres sont occupés par cette partie particulière du modèle simplement parce que le stockage des données est son propre univers, bien étudié.
Cependant, une application a besoin de données agrégées plutôt que normalisées. Les données du domaine doivent être compilées en images telles que: «Bonjour, Ivan!», Ou «Cher Ivan Petrov!», Ou même «Pour Ivan a Petrov a !». Ces données converties sont référées à un autre modèle - modèle de présentation. C'est donc cette partie du modèle qui est encore ignorée par les cadres modernes. Il est ignoré car il n'y a pas d'accord sur la façon de le traiter. Et si les frameworks ne fournissent pas de solution, les programmeurs choisissent le moyen le plus simple: ils jettent le modèle de vue dans le contrôleur. Et ils obtiennent les contrôleurs de graisse détestés mais inévitables!
Total: pour implémenter MVC, vous devez implémenter un modèle de vue. Il n'y a pas d'autre option. Étant donné que les représentations et leurs données peuvent être quelconques, nous déclarons avoir un problème.
Scénario vs contrôleurs de graisse
Il y a un dernier inconvénient des cadres:
- Ils provoquent l'utilisation des soi-disant "contrôleurs épais" contenant à la fois toute la logique: métier, présentation et interaction. Cela détruit complètement l'objectif principal de MVC - la répartition des responsabilités entre les composants de la triade.
Nous arrivons ici aux bases de MVC. Soyons clairs. MVC assume donc la répartition des responsabilités suivante entre les composants de la triade:
- Le contrôleur est la logique d'interaction , c'est-à-dire interactions avec le monde extérieur (demande - réponse) et l'interne (Modèle - Présentation),
- Le modèle est la logique métier , c'est-à-dire générer des données pour une demande spécifique,
- La représentation est la logique de la représentation , c'est-à-dire décoration des données générées par le modèle.
Allez-y. Deux niveaux de responsabilités sont clairement visibles:
- Le niveau organisationnel est le contrôleur,
- Le niveau exécutif est Modèle et représentation.
En termes simples, le contrôleur dirige, charrue modèle et vue. C'est si d'une manière simple. Et sinon d'une manière simple, mais plus précisément? Comment contrôle exactement le contrôleur? Et comment fonctionnent exactement le modèle et la charrue de vue?
Le contrôleur dirige comme ceci:
- Reçoit une demande d'une application,
- Décide quel modèle et quelle vue utiliser pour cette demande,
- Appelle le modèle sélectionné et en reçoit les données,
- Appelle la vue sélectionnée avec les données reçues du modèle,
- Renvoie les données décorées par la vue à l'application.
Quelque chose comme ça. L'essentiel dans ce schéma est que le modèle et la représentation s'avèrent être des maillons de la chaîne d'exécution des requêtes. De plus, par liens successifs: tout d'abord, le modèle convertit la demande en certaines données, puis ces données du modèle sont converties par la vue en une réponse décorée selon les besoins pour une demande spécifique. Par exemple, une demande humanoïde est décorée visuellement avec des modèles, une demande Android est décorée avec des encodeurs JSON.
Essayons maintenant de comprendre comment les artistes labourent exactement - Modèle et présentation. Nous avons dit plus haut qu'il existe un consensus sur la décomposition du modèle en deux sous-composants: le modèle de domaine et le modèle de présentation. Cela signifie qu'il peut y avoir plus d'artistes - pas deux, mais trois. Au lieu d'une chaîne d'exécution
>>
il peut bien y avoir une chaîne
>>
>>
La question se pose: pourquoi seulement deux ou trois? Et si vous en avez besoin de plus? La réponse naturelle est, pour l'amour de Dieu, prenez autant que vous en avez besoin!
D'autres artistes utiles sont immédiatement visibles: validateurs, redirecteurs, divers rendus, et en général tout ce qui est imprévisible, mais agréable.
Récapitulons:
- Le MVC de niveau exécutif (
-
) peut être implémenté comme une chaîne de liens, où chaque lien convertit la sortie du lien précédent en entrée pour le suivant. - L'entrée du premier lien est la demande d'application.
- La sortie du dernier lien est la réponse de l'application à la demande.
J'ai appelé ce Scenario
chaîne, mais pour les maillons de la chaîne, je n'ai pas encore décidé du nom. Les options actuelles sont une scène (dans le cadre d'un script), un filtre (comme convertisseur de données), une action de script. De manière générale, le nom du lien n'est pas si important, il y a une chose plus importante.
Les conséquences de l'apparition du scénario sont importantes. A savoir: Le scénario a assumé la responsabilité principale du contrôleur - déterminer le modèle et la présentation nécessaires à la demande et les lancer. Ainsi, le contrôleur n'a que deux responsabilités: interagir avec le monde extérieur (requête-réponse) et exécuter le script. Et c'est bien dans le sens où tous les composants de la triade MVC sont décomposés séquentiellement et deviennent plus spécifiques et gérables. Et toujours bon à un autre égard - le contrôleur MVCS devient une classe immuable purement interne, et donc, même en principe, ne peut pas devenir gras.
L'utilisation de scénarios conduit à une autre variation du modèle MVC; j'ai appelé cette variation MVCS
- Model-View-Controller-Scenario
.
Et quelques lignes supplémentaires sur la décomposition MVC. Les cadres modernes, où toutes les fonctions typiques sont décomposées à la limite, ont tout naturellement enlevé à la partie conceptuelle MVC des responsabilités d'interaction avec le monde extérieur. Ainsi, des classes spécialement formées comme la HTTP
et le
sont engagées dans le traitement des requêtes des utilisateurs. Par conséquent, le contrôleur ne reçoit pas la demande initiale de l'utilisateur, mais certaines
affinées, ce qui permet d'isoler le contrôleur des spécificités de la demande. De même, l'isolement des spécificités de la réponse HTTP est effectué, permettant au module MVC de définir son propre type de réponse. De plus, les cadres ont mis en œuvre intégralement les deux composants de MVC - le modèle de domaine et le modèle de présentation, cependant, nous en avons déjà discuté. Je suis tout cela au fait que le raffinement et la concrétisation de MVC sont continus et continus, et cela bourdonne.
Exemple MVCS
Voyons maintenant comment l'exemple de Fat Cortroller au début de cet article peut être implémenté dans MVCS.
Nous commençons par créer un contrôleur MVCS:
$mvcs = new MvcsController();
Le contrôleur MVCS reçoit une demande d'un routeur externe. Laissez le routeur convertir l'URI du formulaire «utilisateur / bonjour / XXX» en une telle action et demander des paramètres:
$requestAction = 'user/hello';
Étant donné que le contrôleur MVCS accepte des scripts plutôt que des URI, nous devons mapper certains scripts à l'action de la demande. Il est préférable de le faire dans le conteneur MVCS:
Examinons de plus près ce scénario. Il s'agit d'une chaîne de trois convertisseurs de données séparés par un «>»:
- 'UserModel' est le nom du modèle de domaine 'User', l'entrée du modèle sera les paramètres de demande, la sortie sera les données réelles du modèle,
- "UserViewModel" est le nom du modèle d'affichage qui convertit les données de domaine en données d'affichage,
- 'view, hello' est la vue système 'template' pour un modèle PHP appelé 'hello'.
Il nous suffit maintenant d'ajouter deux transformateurs impliqués dans le script en tant que fonction de fermeture au conteneur MVCS:
Et c'est tout! Pour chaque requête, il est nécessaire de déterminer le script correspondant et toutes ses scènes (à l'exception de celles du système, telles que 'view'). Et rien de plus.
Et maintenant, nous sommes prêts à tester MVCS pour différentes demandes:
L'implémentation PHP MVCS est hébergée sur github.com .
Cet exemple se trouve dans l' example
répertoire MVCS.