Je ne fais que saisir le cadre MVC et je me demande souvent combien de code devrait contenir le modèle. J'ai tendance à avoir une classe d'accès aux données qui a des méthodes comme celle-ci:
public function CheckUsername($connection, $username)
{
try
{
$data = array();
$data['Username'] = $username;
//// SQL
$sql = "SELECT Username FROM" . $this->usersTableName . " WHERE Username = :Username";
//// Execute statement
return $this->ExecuteObject($connection, $sql, $data);
}
catch(Exception $e)
{
throw $e;
}
}
Mes modèles ont tendance à être une classe d'entités mappée à la table de base de données.
Est-ce que l'objet modèle doit avoir toutes les propriétés mappées de la base de données ainsi que le code ci-dessus ou est-il correct de séparer ce code qui fonctionne réellement la base de données?
Vais-je finir par avoir quatre couches?
php
oop
model-view-controller
architecture
model
Dietpixel
la source
la source
Exception
n'a pas beaucoup de valeur documentaire. Personnellement, si je descendais sur cette voie, je choisirais PHPDoc@exception
, ou un mécanisme similaire, donc cela apparaît dans la documentation générée.Réponses:
La première chose que je dois clarifier est: le modèle est une couche .
Deuxièmement: il existe une différence entre le MVC classique et ce que nous utilisons dans le développement Web. Voici un peu d'une réponse plus ancienne que j'ai écrite, qui décrit brièvement en quoi elles sont différentes.
Ce qu'un modèle n'est PAS:
Le modèle n'est pas une classe ou un objet unique. C'est une erreur très courante à commettre (moi aussi, bien que la réponse originale ait été écrite quand j'ai commencé à apprendre le contraire) , car la plupart des frameworks perpétuent cette idée fausse.
Ce n'est pas non plus une technique de cartographie relationnelle-objet (ORM) ni une abstraction des tables de base de données. Quiconque vous dit le contraire essaie très probablement de «vendre» un autre ORM flambant neuf ou un framework entier.
Qu'est-ce qu'un modèle:
Dans une adaptation MVC appropriée, le M contient toute la logique métier du domaine et la couche modèle est principalement constituée de trois types de structures:
Objets de domaine
Ce serait là que vous définissez comment valider les données avant d'envoyer une facture, ou pour calculer le coût total d'une commande. Dans le même temps, les objets de domaine ignorent complètement le stockage - ni d' où (base de données SQL, API REST, fichier texte, etc.) ni même s'ils sont enregistrés ou récupérés.
Mappeurs de données
Ces objets ne sont responsables que du stockage. Si vous stockez des informations dans une base de données, ce serait là que réside le SQL. Ou peut-être utilisez-vous un fichier XML pour stocker des données, et vos Data Mappers analysent à partir et vers des fichiers XML.
Prestations de service
Vous pouvez les considérer comme des "objets de domaine de niveau supérieur", mais au lieu de la logique métier, les services sont responsables de l'interaction entre les objets de domaine et les mappeurs . Ces structures finissent par créer une interface "publique" pour interagir avec la logique métier du domaine. Vous pouvez les éviter, mais à peine de divulguer une logique de domaine dans les contrôleurs .
Il existe une réponse connexe à ce sujet dans la question sur la mise en œuvre de l' ACL - elle pourrait être utile.
La communication entre la couche modèle et d'autres parties de la triade MVC ne doit se faire que via les services . La séparation claire présente quelques avantages supplémentaires:
Comment interagir avec un modèle?
Accéder aux instances de service
Pour que les instances View et Controller (ce que vous pourriez appeler: "couche UI") aient accès à ces services, il existe deux approches générales:
Comme vous vous en doutez, le conteneur DI est une solution beaucoup plus élégante (tout en n'étant pas la plus simple pour un débutant). Les deux bibliothèques que je recommande de considérer pour cette fonctionnalité seraient le composant DependencyInjection autonome de Syfmony ou Auryn .
Les solutions utilisant une usine et un conteneur DI vous permettent également de partager les instances de divers serveurs à partager entre le contrôleur sélectionné et de visualiser un cycle de demande-réponse donné.
Modification de l'état du modèle
Maintenant que vous pouvez accéder à la couche modèle dans les contrôleurs, vous devez commencer à les utiliser:
Vos contrôleurs ont une tâche très claire: prendre l'entrée utilisateur et, sur la base de cette entrée, changer l'état actuel de la logique métier. Dans cet exemple, les états qui sont modifiés entre "utilisateur anonyme" et "utilisateur connecté".
Le contrôleur n'est pas responsable de la validation de l'entrée de l'utilisateur, car cela fait partie des règles métier et le contrôleur n'appelle certainement pas des requêtes SQL, comme ce que vous verriez ici ou ici (veuillez ne pas les détester, elles sont erronées, pas mal).
Affichage à l'utilisateur du changement d'état.
Ok, l'utilisateur s'est connecté (ou a échoué). Maintenant quoi? Cet utilisateur l'ignore encore. Vous devez donc produire une réponse et c'est la responsabilité d'un point de vue.
Dans ce cas, la vue a produit l'une des deux réponses possibles, en fonction de l'état actuel de la couche modèle. Pour un cas d'utilisation différent, vous auriez la vue en choisissant différents modèles à restituer, basés sur quelque chose comme "l'article actuellement sélectionné".
La couche de présentation peut en fait être assez élaborée, comme décrit ici: Comprendre les vues MVC en PHP .
Mais je fais juste une API REST!
Bien sûr, il y a des situations où c'est une surpuissance.
MVC est juste une solution concrète pour le principe de séparation des préoccupations . MVC sépare l'interface utilisateur de la logique métier et, dans l'interface utilisateur, elle a séparé la gestion des entrées utilisateur et de la présentation. C'est crucial. Bien que les gens la décrivent souvent comme une "triade", elle n'est pas composée de trois parties indépendantes. La structure ressemble plus à ceci:
Cela signifie que, lorsque la logique de votre couche de présentation est presque inexistante, l'approche pragmatique consiste à les conserver en tant que couche unique. Il peut également simplifier considérablement certains aspects de la couche modèle.
En utilisant cette approche, l'exemple de connexion (pour une API) peut être écrit comme suit:
Bien que cela ne soit pas durable, lorsque vous avez une logique compliquée pour le rendu d'un corps de réponse, cette simplification est très utile pour des scénarios plus triviaux. Mais attention , cette approche deviendra un cauchemar, quand on tentera de l'utiliser dans de grandes bases de code avec une logique de présentation complexe.
Comment construire le modèle?
Puisqu'il n'y a pas une seule classe "Model" (comme expliqué ci-dessus), vous ne "construisez pas vraiment le modèle". Au lieu de cela, vous commencez à créer des services , qui sont capables d'exécuter certaines méthodes. Et puis implémentez les objets de domaine et les mappeurs .
Un exemple de méthode de service:
Dans les deux approches ci-dessus, il y avait cette méthode de connexion pour le service d'identification. À quoi cela ressemblerait-il réellement? J'utilise une version légèrement modifiée de la même fonctionnalité d' une bibliothèque , que j'ai écrite .. parce que je suis paresseux:
Comme vous pouvez le voir, à ce niveau d'abstraction, il n'y a aucune indication d'où les données ont été extraites. Il peut s'agir d'une base de données, mais il peut également s'agir simplement d'un objet factice à des fins de test. Même les mappeurs de données, qui sont réellement utilisés pour cela, sont cachés dans les
private
méthodes de ce service.Façons de créer des mappeurs
Pour implémenter une abstraction de persistance, les approches les plus flexibles consistent à créer des mappeurs de données personnalisés .
De: livre PoEAA
En pratique, ils sont mis en œuvre pour l'interaction avec des classes ou des superclasses spécifiques. Disons que vous avez
Customer
etAdmin
dans votre code (tous deux héritant d'uneUser
superclasse). Les deux finiraient probablement par avoir un mappeur de correspondance distinct, car ils contiennent des champs différents. Mais vous vous retrouverez également avec des opérations partagées et couramment utilisées. Par exemple: mettre à jour l' heure de la "dernière vue en ligne" . Et au lieu de rendre les mappeurs existants plus compliqués, l'approche la plus pragmatique est d'avoir un "User Mapper" général, qui ne met à jour que l'horodatage.Quelques commentaires supplémentaires:
Tables et modèle de base de données
Bien qu'il existe parfois une relation directe 1: 1: 1 entre une table de base de données, un objet de domaine et un mappeur , dans des projets plus importants, il peut être moins courant que prévu:
Les informations utilisées par un seul objet de domaine peuvent être mappées à partir de différentes tables, tandis que l'objet lui-même n'a aucune persistance dans la base de données.
Exemple: si vous générez un rapport mensuel. Cela collecterait des informations à partir de différentes tables, mais il n'y a pas de
MonthlyReport
table magique dans la base de données.Un mappeur unique peut affecter plusieurs tables.
Exemple: lorsque vous stockez des données de l'
User
objet, cet objet de domaine peut contenir une collection d'autres objets de domaine - desGroup
instances. Si vous les modifiez et les stockezUser
, le Data Mapper devra mettre à jour et / ou insérer des entrées dans plusieurs tables.Les données d'un seul objet de domaine sont stockées dans plusieurs tables.
Exemple: dans les grands systèmes (pensez: un réseau social de taille moyenne), il peut être pragmatique de stocker les données d'authentification des utilisateurs et les données souvent consultées séparément des gros morceaux de contenu, ce qui est rarement nécessaire. Dans ce cas, il se peut que vous ayez toujours une seule
User
classe, mais les informations qu'elle contient dépendent de l'extraction des détails complets.Pour chaque objet de domaine, il peut y avoir plus d'un mappeur
Exemple: vous avez un site d'actualités avec un code partagé basé à la fois pour le public et le logiciel de gestion. Mais, alors que les deux interfaces utilisent la même
Article
classe, la gestion a besoin de beaucoup plus d'informations. Dans ce cas, vous auriez deux mappeurs distincts: "interne" et "externe". Chacun effectuant des requêtes différentes, ou même utilise des bases de données différentes (comme en maître ou en esclave).Une vue n'est pas un modèle
Les instances de vue dans MVC (si vous n'utilisez pas la variation MVP du modèle) sont responsables de la logique de présentation. Cela signifie que chaque vue jonglera généralement avec au moins quelques modèles. Il acquiert des données de la couche modèle puis, en fonction des informations reçues, choisit un modèle et définit des valeurs.
L'un des avantages que vous en retirez est la réutilisation. Si vous créez une
ListView
classe, alors, avec un code bien écrit, vous pouvez avoir la même classe en remettant la présentation de la liste d'utilisateurs et des commentaires sous un article. Parce qu'ils ont tous deux la même logique de présentation. Vous changez simplement de modèle.Vous pouvez utiliser des modèles PHP natifs ou utiliser un moteur de modélisation tiers. Il peut également exister des bibliothèques tierces, capables de remplacer complètement les instances de View .
Qu'en est-il de l'ancienne version de la réponse?
Le seul changement majeur est que, ce qui est appelé modèle dans l'ancienne version, est en fait un service . Le reste de "l'analogie de la bibliothèque" se maintient assez bien.
Le seul défaut que je vois est que ce serait une bibliothèque vraiment étrange, car elle vous renverrait des informations du livre, mais ne vous laisserait pas toucher le livre lui-même, car sinon l'abstraction commencerait à "fuir". Je devrais peut-être penser à une analogie plus appropriée.
Quelle est la relation entre les instances View et Controller ?
La structure MVC est composée de deux couches: ui et model. Les structures principales de la couche d'interface utilisateur sont les vues et le contrôleur.
Lorsque vous traitez avec des sites Web qui utilisent le modèle de conception MVC, la meilleure façon est d'avoir une relation 1: 1 entre les vues et les contrôleurs. Chaque vue représente une page entière de votre site Web et dispose d'un contrôleur dédié pour gérer toutes les demandes entrantes pour cette vue particulière.
Par exemple, pour représenter un article ouvert, vous auriez
\Application\Controller\Document
et\Application\View\Document
. Cela contiendrait toutes les fonctionnalités principales de la couche d'interface utilisateur, lorsqu'il s'agit de traiter des articles (bien sûr, vous pourriez avoir certains composants XHR qui ne sont pas directement liés aux articles) .la source
Tout ce qui relève de la logique métier appartient à un modèle, qu'il s'agisse d'une requête de base de données, de calculs, d'un appel REST, etc.
Vous pouvez avoir l'accès aux données dans le modèle lui-même, le modèle MVC ne vous y empêche pas. Vous pouvez le recouvrir de services, de mappeurs et de quoi d'autre, mais la définition réelle d'un modèle est une couche qui gère la logique métier, rien de plus, rien de moins. Cela peut être une classe, une fonction ou un module complet avec un gazillion d'objets si c'est ce que vous voulez.
Il est toujours plus facile d'avoir un objet séparé qui exécute réellement les requêtes de base de données au lieu de les exécuter directement dans le modèle: cela sera particulièrement utile lors des tests unitaires (en raison de la facilité d'injection d'une dépendance de base de données factice dans votre modèle):
De plus, en PHP, vous devez rarement intercepter / renvoyer les exceptions car la trace est préservée, en particulier dans un cas comme votre exemple. Laissez simplement l'exception être levée et rattrapez-la à la place dans le contrôleur.
la source
User
classe étend essentiellement le modèle, mais ce n'est pas un objet. L'utilisateur doit être un objet et possède des propriétés telles que: id, nom ... Vous déployez laUser
classe est une aide.User
représente un objet, et il devrait avoir les propriétés d'un utilisateur, pas des méthodes commeCheckUsername
, que devez-vous faire si vous souhaitez créer un nouvelUser
objet?new User($db)
Dans Web- "MVC", vous pouvez faire ce que vous voulez.
Le concept original (1) décrivait le modèle comme la logique métier. Il doit représenter l'état de l'application et appliquer une certaine cohérence des données. Cette approche est souvent décrite comme un «modèle gras».
La plupart des frameworks PHP suivent une approche plus superficielle, où le modèle n'est qu'une interface de base de données. Mais à tout le moins, ces modèles devraient encore valider les données et les relations entrantes.
Quoi qu'il en soit, vous n'êtes pas très loin si vous séparez les éléments SQL ou les appels de base de données dans une autre couche. De cette façon, vous n'avez qu'à vous préoccuper des données / comportements réels, pas de l'API de stockage réelle. (Il est cependant déraisonnable d'en faire trop. Par exemple, vous ne pourrez jamais remplacer un backend de base de données par un stockage de fichiers s'il n'a pas été conçu à l'avance.)
la source
Le plus souvent, la plupart des applications auront des données, une partie d'affichage et de traitement et nous les mettons simplement dans les lettres
M
,V
etC
.Model (
M
) -> Possède les attributs qui contiennent l'état d'application et ne savent rien deV
etC
.View (
V
) -> A un format d'affichage pour l'application et ne connaît que le modèle de digestion et ne se soucie pasC
.Contrôleur (
C
) ----> A une partie de l'application de traitement et agit comme un câblage entre M et V et cela dépend des deuxM
,V
contrairement àM
etV
.Dans l'ensemble, il existe une séparation des préoccupations entre chacun. À l'avenir, tout changement ou amélioration pourra être ajouté très facilement.
la source
Dans mon cas, j'ai une classe de base de données qui gère toutes les interactions directes de base de données telles que l'interrogation, la récupération, etc. Donc, si je devais changer ma base de données de MySQL à PostgreSQL, il n'y aura aucun problème. Il peut donc être utile d'ajouter cette couche supplémentaire.
Chaque table peut avoir sa propre classe et ses méthodes spécifiques, mais pour obtenir réellement les données, elle laisse la classe de base de données les gérer:
Fichier
Database.php
Objet de table classL
J'espère que cet exemple vous aide à créer une bonne structure.
la source
Database
dans l'exemple n'est pas une classe. C'est juste un wrapper pour les fonctions. De plus, comment pouvez-vous avoir une "classe d'objets table" sans objet?