Comment un modèle devrait-il être structuré dans MVC?

je commence juste à saisir le cadre MVC et je me demande souvent combien de code devrait aller dans le modèle. J'ai tendance à avoir un accès aux données de la classe qui a des méthodes comme ceci:

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'entity qui est mappée à la table de base de données.

est-ce que l'objet model devrait avoir toutes les propriétés cartographiées de la base de données ainsi que le code ci-dessus ou est-il correct de séparer ce code qui fait réellement fonctionner la base de données?

est-ce que je vais avoir quatre couches?

517
demandé sur i alarmed alien 2011-05-03 04:28:42
la source

5 ответов

Disclaimer: ce qui suit est une description de la façon dont je comprends MVC-like patterns dans le contexte des applications Web basées sur PHP. Tous les liens externes qui sont utilisés dans le contenu sont là pour expliquer les termes et les concepts, et pas impliquer mon propre crédibilité sur le sujet.

la première chose que je dois clarifier est: le modèle est une couche .

deuxième: il y a une différence entre classique MVC et ce que nous utilisons dans le développement web. Voici un peu d'un ancien réponse que j'ai écrit, qui décrit brièvement la manière dont ils sont différents.

Ce modèle n'est PAS:

Le modèle n'est pas une classe ou un objet unique. C'est une erreur très commune pour faire (je l'ai fait aussi, bien que l'original la réponse a été écrit quand j'ai commencé à apprendre autrement) , parce que la plupart des cadres de perpétuer cette idée fausse.

il ne s'agit pas non plus d'une technique de cartographie objet-relationnelle (ORM) ni d'une abstraction de tables de base de données. Quiconque vous dit le contraire est le plus susceptible d'essayer de "vendre" un autre ORM tout neuf ou un cadre entier.

Quel modèle est:

dans l'adaptation MVC appropriée, le M contient toute la logique d'affaires de domaine et la couche modèle est la plupart du temps fait à partir de trois types de structures:

  • Objets Du Domaine

    un objet de domaine est un conteneur logique d'informations purement de domaine; il représente habituellement une entité logique dans l'espace de domaine de problème. Communément appelé logique d'entreprise .

    Ce serait où vous définissez comment valider les données avant l'envoi de la facture, ou pour calculer le coût total d'une commande. En même temps, Domain Objects ne sont pas du tout conscients de stockage - ni de (base de données SQL, API REST, fichier texte, etc.) ni même si ils sont sauvés ou récupérés.

  • Les Données Mappeurs

    ces objets ne sont responsables que du stockage. Si vous stockez des informations dans une base de données, ce serait là où le SQL vit. Ou peut-être que vous utilisez un fichier XML pour stocker des données, et votre "Data Mappers parsing à partir de et vers des fichiers XML.

  • Services

    vous pouvez les considérer comme des" objets de domaine de niveau supérieur", mais au lieu de la logique d'affaires, Services sont responsables de l'interaction entre objets de domaine et Mappers . Ces structures finissent par créer une interface "publique" pour interagir avec la logique opérationnelle du domaine. Vous pouvez les éviter, mais à la peine de fuite de la logique du domaine dans les contrôleurs .

    il y a une réponse connexe à ce sujet dans la question ACL implementation - cela pourrait être utile.

la communication entre la couche modèle et les autres parties de la triade MVC ne doit se faire que par Services . La séparation claire a quelques avantages supplémentaires:

  • il contribue à faire respecter les principe de responsabilité unique (PRS)
  • fournit un "wiggle room" supplémentaire dans le cas où la logique change
  • maintient le contrôleur aussi simple que possible
  • donne un modèle clair, si jamais vous avez besoin d'une API externe

Comment interagir avec un modèle?

"1519210920 conditions Préalables": regarder des conférences Globale "de l'État et des Singletons" et "Ne Cherchez pas les Choses!" à partir de la Propre Code Parle.

accès aux instances de service

pour les deux instances View et Controller (ce que vous pourriez appeler: "UI layer") pour avoir accès à ces services, il y a deux approches générales:

  1. vous pouvez injecter les services requis dans les constructeurs de vos vues et contrôleurs directement, de préférence en utilisant un conteneur DI.
  2. utiliser une usine pour des services comme une dépendance obligatoire pour toutes vos vues et contrôleurs.

comme vous pourriez le penser, le conteneur DI est une solution beaucoup plus élégante (bien que n'étant pas la plus facile pour un débutant.) Les deux bibliothèques, que je recommande d'envisager pour cette fonctionnalité serait autonome de Syfmony composant D'injection de dépendance ou Auryn .

les deux solutions utilisant une usine et un conteneur DI vous permettraient également de partager les instances de divers serveurs à partager entre le contrôleur sélectionné et la vue pour un cycle de requête-réponse donné.

modification de l'état du modèle

maintenant que vous pouvez accéder à la couche model dans les controllers, vous devez commencer à les utiliser réellement:

public function postLogin(Request $request)
{
    $email = $request->get('email');
    $identity = $this->identification->findIdentityByEmailAddress($email);
    $this->identification->loginWithPassword(
        $identity,
        $request->get('password')
    );
}

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 commerciale. Dans cet exemple, les états qui sont modifiés entre sont "utilisateur anonyme" et "utilisateur".

contrôleur n'est pas responsable de la validation des entrées de l'utilisateur, parce que Cela fait partie des règles d'affaires et le contrôleur n'appelle certainement pas les requêtes SQL, comme ce que vous verriez ici ou ici (s'il vous plaît ne les détestez pas, ils sont mal orientés, pas mauvais).

montrant à l'utilisateur le changement d'état.

Ok, l'utilisateur s'est connecté (ou échoué). et maintenant? dit que l'utilisateur n'en est pas encore conscient. Donc, vous devez réellement produire une réponse et c'est le la responsabilité de vue.

public function postLogin()
{
    $path = '/login';
    if ($this->identification->isUserLoggedIn()) {
        $path = '/dashboard';
    }
    return new RedirectResponse($path); 
}

dans ce cas, la vue a produit l'une des deux réponses possibles, basée sur l'état actuel de la couche modèle. Pour un cas d'utilisation différent, vous auriez la vue choisissant différents modèles à rendre, basé sur quelque chose comme" courant sélectionné de l'article".

la couche de présentation peut en fait obtenir très élaboré, comme décrit ici: compréhension MVC vues en PHP .

mais je fais juste une API de repos!

bien sûr, il y a des situations, quand il s'agit d'une overkill.

MVC est juste une solution concrète pour séparation des préoccupations Principe. MVC sépare l'interface utilisateur de la logique d'entreprise, et il dans L'UI, il a séparé la gestion de l'entrée de l'utilisateur et la présentation. c'est crucial. Alors que les gens le décrivent souvent comme une "triade"", ce n'est pas vraiment constitué de trois parties indépendantes. La structure est plus comme ceci:

MVC separation

cela signifie que, lorsque la logique de votre couche de présentation est quasi inexistante, l'approche pragmatique consiste à les garder comme couche unique. Elle peut aussi 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:

public function postLogin(Request $request)
{
    $email = $request->get('email');
    $data = [
        'status' => 'ok',
    ];
    try {
        $identity = $this->identification->findIdentityByEmailAddress($email);
        $token = $this->identification->loginWithPassword(
            $identity,
            $request->get('password')
        );
    } catch (FailedIdentification $exception) {
        $data = [
            'status' => 'error',
            'message' => 'Login failed!',
        ]
    }

    return new JsonResponse($data);
}

bien que cela ne soit pas soutenable, lorsque vous avez compliqué la logique pour rendre un corps de réponse, cette simplification est très utile pour des scénarios plus triviaux. Mais être averti , cette approche deviendra un cauchemar, en essayant d'utiliser dans les grandes bases de codes avec une logique de présentation complexe.

Comment construire le modèle?

Puisqu'il n'y a pas une seule classe "modèle" (comme expliqué ci-dessus), vous ne "construisez pas le modèle". Au lieu de cela, vous commencez à faire Services , qui sont en mesure d'effectuer certaines méthodes. Et puis mettre en œuvre Domain Objects et Mappers .

exemple d'une méthode de service:

dans les deux approches ci-dessus, il y avait cette méthode de connexion pour le service d'identification. Qu'en serait-il vraiment l'air. Je suis à l'aide d'un version légèrement modifiée de la même fonctionnalité de une bibliothèque , que j'ai écrit .. parce que je suis paresseux:

public function loginWithPassword(Identity $identity, string $password): string
{
    if ($identity->matchPassword($password) === false) {
        $this->logWrongPasswordNotice($identity, [
            'email' => $identity->getEmailAddress(),
            'key' => $password, // this is the wrong password
        ]);

        throw new PasswordMismatch;
    }

    $identity->setPassword($password);
    $this->updateIdentityOnUse($identity);
    $cookie = $this->createCookieIdentity($identity);

    $this->logger->info('login successful', [
        'input' => [
            'email' => $identity->getEmailAddress(),
        ],
        'user' => [
            'account' => $identity->getAccountId(),
            'identity' => $identity->getId(),
        ],
    ]);

    return $cookie->getToken();
}

comme vous pouvez le voir, à ce niveau d'abstraction, il n'y a aucune indication d'où les données ont été extraites. Il pourrait être une base de données, mais il peut également être simplement un objet fantaisie des fins de test. Même les mappeurs de données, qui sont effectivement utilisés pour cela, sont cachés dans les méthodes private de ce service.

private function changeIdentityStatus(Entity\Identity $identity, int $status)
{
    $identity->setStatus($status);
    $identity->setLastUsed(time());
    $mapper = $this->mapperFactory->create(Mapper\Identity::class);
    $mapper->store($identity);
}

façons de créer des mappers

pour mettre en œuvre une abstraction de persistance, sur les approches les plus flexibles est de créer personnalisé data mappers .

Mapper diagram

De: PoEAA livre

dans la pratique, ils sont mis en œuvre pour l'interaction avec des classes ou superclasse. Disons que vous avez Customer et Admin dans votre code (tous deux héritant d'une superclasse User ). Les deux finiraient probablement par avoir un mapper séparé, puisqu'ils contiennent des champs différents. Mais vous finirez aussi avec des opérations partagées et d'usage courant. Par exemple: Mise à jour du "vu pour la dernière fois en ligne" time. Et au lieu de rendre les contributeurs existants plus complexes, l'approche plus pragmatique est d'avoir un "Utilisateur Mappeur", ce qui ne fait que mettre à jour l'horodatage.

quelques commentaires supplémentaires:

  1. tables de base de données et modèle

    alors que parfois il existe une relation directe 1:1:1 entre une table de base de données, Domain Object , et Mapper , dans les plus grands projets, il pourrait être moins commun que vous attendez:

    • Information utilisée par un seul Objet de Domaine peuvent être mappés à partir de tables différentes, tandis que l'objet lui-même n'a pas de persistance dans la base de données.

      exemple: si vous produisez un rapport mensuel. Cela permettrait de recueillir de l'information à partir de différents tableaux, mais il n'y a pas de table magique MonthlyReport dans la base de données.

    • Un seul Mapper" peut affecter plusieurs tables.

      exemple: lorsque vous stockez des données à partir de l'objet User , cet objet de domaine pourrait contenir la collecte d'autres objets de domaine - Group instances. Si vous les modifiez et stockez le User , 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 plus d'un tableau.

      exemple: dans les grands systèmes (penser: un réseau social de taille moyenne), il pourrait être pragmatique de stocker les données d'authentification de l'utilisateur et les données souvent accessibles séparément de plus grands morceaux de contenu, ce qui est rarement nécessaire. Dans ce cas, vous pourriez encore avoir une seule classe User , mais les informations qu'elle contient dépendraient de savoir si tous les détails ont été extraits.

    • pour chaque objet du domaine il peut y avoir plus d'un mapper

      exemple: vous avez un site d'information avec un codebasé partagé pour public-facing et le logiciel de gestion. Mais, alors que les deux interfaces utilisent la même classe Article , la direction a besoin de beaucoup plus d'informations. Dans ce cas, vous avez deux mappeurs: "internes" et "externe". Chacune effectuant des requêtes différentes, ou même utilisant des bases de données différentes (comme dans master ou slave).

  2. Un point de vue n'est pas un modèle

    View les instances dans MVC (si vous n'utilisez pas la variation MVP du pattern) sont responsables de la logique de présentation. Cela signifie que chaque voir sera généralement jongler avec au moins quelques modèles. Il acquiert des données de la couche modèle et puis, sur la base des informations reçues, choisit un modèle et fixe des valeurs.

    L'un des avantages que vous en retirez est la réutilisation. Si vous créez une classe ListView , alors, avec du code bien écrit, Vous pouvez avoir la même classe en passant la présentation de la liste des utilisateurs et des commentaires ci-dessous un article. Parce qu'ils ont tous deux la même logique de présentation. Vous venez de passer modèle.

    vous pouvez utiliser soit modèles PHP natifs ou utiliser un moteur de templating tiers. Il pourrait également y avoir des bibliothèques tierces, qui sont en mesure de remplacer entièrement voir instances.

  3. 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 bibliothèque" se maintient assez bien.

    le seul défaut que je vois est que ce serait une bibliothèque vraiment étrange, parce qu'elle vous retournerait des informations du livre, mais ne vous laisserait pas toucher le livre lui-même, parce que sinon l'abstraction commencerait à "fuir". Je devrais peut-être penser à une analogie plus appropriée.

  4. Quelle est la relation entre Vue et Contrôleur instances?

    la structure MVC est composée de deux couches: l'interface utilisateur et le modèle. Les principales structures de la couche UI sont les vues et le contrôleur.

    lorsque vous avez affaire à des sites Web qui utilisent MVC design pattern, 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 dans votre site web et il a un contrôleur dédié pour traiter toutes les requêtes 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 pour la couche UI, quand il s'agit de traiter les articles (bien sûr, vous pourriez avoir quelques XHR composants qui ne sont pas directement liés aux articles) .

833
répondu tereško 2017-10-18 10:54:33
la source

Tout ce qui est business logic appartient à un modèle, qu'il s'agisse d'une requête de base de données, de calculs, d'un appel de repos, etc.

vous pouvez avoir l'accès aux données dans le modèle lui-même, le modèle MVC ne vous empêche pas de le faire. Vous pouvez l'enduire de sucre avec des services, des mappers et ce qui ne l'est pas, mais la définition réelle d'un modèle est une couche qui gère la logique des affaires, rien de plus, rien de moins. Il peut être une classe, une fonction, ou d'un module avec une foule 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 la base de données au lieu de les avoir exécutées directement dans le model: cela sera particulièrement pratique lors des tests unitaires (en raison de la facilité d'injection d'une dépendance de base de données simulée dans votre model):

class Database {
   protected $_conn;

   public function __construct($connection) {
       $this->_conn = $connection;
   }

   public function ExecuteObject($sql, $data) {
       // stuff
   }
}

abstract class Model {
   protected $_db;

   public function __construct(Database $db) {
       $this->_db = $db;
   }
}

class User extends Model {
   public function CheckUsername($username) {
       // ...
       $sql = "SELECT Username FROM" . $this->usersTableName . " WHERE ...";
       return $this->_db->ExecuteObject($sql, $data);
   }
}

$db = new Database($conn);
$model = new User($db);
$model->CheckUsername('foo');

aussi, en PHP, vous avez rarement besoin d'attraper / rethrow exceptions parce que la rétrotrace est préservé, surtout dans un cas comme le votre exemple. Il suffit de lancer l'exception et de l'attraper dans le contrôleur à la place.

33
répondu netcoder 2015-06-27 22:38:55
la source

Dans le Web"MVC" vous pouvez faire ce que vous s'il vous plaît.

le concept original (1) décrit le modèle comme étant la logique opérationnelle. Il doit représenter l'état de l'application et assurer une certaine cohérence des données. Cette approche est souvent décrite comme un "modèle de graisse".

la plupart des cadres 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 toujours valider les données entrantes et les relations.

dans tous les cas, vous n'êtes pas très loin si vous séparez les appels SQL ou les appels de base de données dans une autre couche. De cette façon, vous ne devez vous préoccuper que des données réelles/du comportement, pas de l'API de stockage réelle. (Il est toutefois déraisonnable d'en faire trop. Vous ne pourrez par exemple jamais remplacer un backend de base de données par un filestorage si celui-ci n'a pas été conçu à l'avance.)

19
répondu mario 2017-05-23 15:34:45
la source

la plupart des applications ont souvent une partie de données,d'affichage et de traitement et nous avons juste mis tous ceux-là dans les lettres M , V et C .

modèle( M ) -->a les attributs qui détiennent l'état d'application et il ne sait rien au sujet de V et C .

View( V ) -->a le format d'affichage pour le application et et sait seulement comment-digérer le modèle sur elle et ne se soucie pas de C .

contrôleur( C ) ---->a une partie de traitement de l'application et agit comme le câblage entre M et V et il dépend à la fois M , V contrairement à M et V .

au total, il y a une séparation des préoccupations entre chacune. À l'avenir, tout changement ou amélioration peut être ajouté très facilement.

5
répondu feel good and programming 2014-08-19 18:44:27
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 la recherche, la recherche, et ainsi de suite. Donc si je devais changer ma base de données de MySQL à PostgreSQL il n'y aurait pas de problème. Donc, ajouter cette couche supplémentaire peut être utile.

chaque table peut avoir sa propre classe et avoir ses méthodes spécifiques, mais pour obtenir réellement les données, il laisse la classe de base de données la manipuler:

Fichier Database.php

class Database {
    private static $connection;
    private static $current_query;
    ...

    public static function query($sql) {
        if (!self::$connection){
            self::open_connection();
        }
        self::$current_query = $sql;
        $result = mysql_query($sql,self::$connection);

        if (!$result){
            self::close_connection();
            // throw custom error
            // The query failed for some reason. here is query :: self::$current_query
            $error = new Error(2,"There is an Error in the query.\n<b>Query:</b>\n{$sql}\n");
            $error->handleError();
        }
        return $result;
    }
 ....

    public static function find_by_sql($sql){
        if (!is_string($sql))
            return false;

        $result_set = self::query($sql);
        $obj_arr = array();
        while ($row = self::fetch_array($result_set))
        {
            $obj_arr[] = self::instantiate($row);
        }
        return $obj_arr;
    }
}

objet de la Table classL

class DomainPeer extends Database {

    public static function getDomainInfoList() {
        $sql = 'SELECT ';
        $sql .='d.`id`,';
        $sql .='d.`name`,';
        $sql .='d.`shortName`,';
        $sql .='d.`created_at`,';
        $sql .='d.`updated_at`,';
        $sql .='count(q.id) as queries ';
        $sql .='FROM `domains` d ';
        $sql .='LEFT JOIN queries q on q.domainId = d.id ';
        $sql .='GROUP BY d.id';
        return self::find_by_sql($sql);
    }

    ....
}

j'espère que cet exemple vous aidera à créer une bonne structure.

0
répondu Ibu 2012-06-15 00:24:06
la source