Comment déterminer ce qui devrait obtenir son propre contrôleur respectif?

10

J'utilise le modèle MVC dans mon application Web construite avec PHP.

J'ai toujours du mal à déterminer si j'ai besoin d'un nouveau contrôleur dédié pour un ensemble d'actions ou si je dois les placer à l'intérieur d'un contrôleur déjà existant.

Y a-t-il de bonnes règles générales à suivre lors de la création de contrôleurs?

Par exemple, je peux avoir:

AuthenticationController avec des actions:

  • index() pour afficher le formulaire de connexion.
  • submit() pour gérer la soumission du formulaire.
  • logout(), explicite.

OU

LoginController avec des actions:

  • index() pour afficher le formulaire de connexion.
  • submit() pour gérer la soumission du formulaire.

LogoutController avec action:

  • index() pour gérer la déconnexion.

OU

AccountController avec des actions:

  • loginGet() pour afficher le formulaire de connexion.
  • loginPost() pour gérer la soumission du formulaire de connexion.
  • logoutGet() pour gérer la déconnexion.
  • registerGet() pour afficher le formulaire d'inscription.
  • registerPost() pour gérer la soumission du formulaire.

    Et toute autre action impliquée dans un compte.

Kid Diamond
la source
Jetez peut-être un œil au design RESTful. Il ne résout pas tous les problèmes de ce type, mais vous donne une très bonne direction pour y penser.
thorsten müller

Réponses:

3

Afin de trouver le bon regroupement pour les contrôleurs, pensez aux tests .

(Même si vous ne faites aucun test, penser à la façon dont vous allez procéder pour tester vos contrôleurs vous donnera de très bonnes informations sur la façon de les structurer.)

An AuthenticationControllern'est pas testable en soi, car il ne contient que des fonctionnalités de connexion et de déconnexion, mais votre code de test devra en quelque sorte créer de faux comptes à des fins de test avant de pouvoir tester une connexion réussie. Vous pouvez contourner le sous-système en cours de test et aller directement à votre modèle pour la création des comptes de test, mais vous aurez alors un test fragile entre vos mains: si le modèle change, vous devrez modifier non seulement le code des tests le modèle, mais aussi le code qui teste le contrôleur, même si l'interface et le comportement du contrôleur sont restés inchangés. C'est déraisonnable.

A LoginControllerne convient pas pour les mêmes raisons: vous ne pouvez pas le tester sans créer de comptes au préalable, et il y a encore plus de choses que vous ne pouvez pas tester, comme par exemple empêcher les doublons de connexion mais ensuite permettre à un utilisateur de se connecter après s'être déconnecté. (Étant donné que ce contrôleur n'a pas de fonctionnalité de déconnexion.)

Un AccountControllervous donnera tout ce dont vous avez besoin pour faire vos tests: vous pouvez créer un compte de test puis essayer de vous connecter, vous pouvez supprimer le compte puis vous assurer que vous ne pouvez plus vous connecter, vous pouvez changer le mot de passe et vous assurer que le le bon mot de passe doit être utilisé pour se connecter, etc.

Pour conclure: afin d'écrire même la plus petite suite de tests, vous devrez mettre à disposition toutes les fonctionnalités de la AccountController. La subdiviser en contrôleurs plus petits semble produire des contrôleurs handicapés avec des fonctionnalités insuffisantes pour un test approprié. C'est une très bonne indication que la fonctionnalité de AccountControllerest la plus petite subdivision qui ait du sens.

Et d'une manière générale, l'approche «pensez aux tests» fonctionnera non seulement dans ce scénario particulier, mais dans tout scénario similaire que vous rencontrerez à l'avenir.

Mike Nakis
la source
1

La réponse n'est pas si évidente

Permettez-moi de clarifier quelques points avant de répondre à vos questions. Tout d'abord:

Qu'est-ce que le contrôleur?

Le contrôleur fait partie du système qui contrôle la demande - après l'envoi. Ainsi, nous pouvons le définir comme un ensemble d'actions liées à ... quoi?

Quelle est la portée du contrôleur?

Et c'est plus ou moins une partie quand nous aurons une réponse. Qu'est-ce que tu penses? Est-ce un contrôleur de choses (par exemple un compte) ou un contrôleur d'actions? Bien sûr, c'est un contrôleur d'un modèle ou d'une chose plus abstraite qui fournit des actions dessus.

La réponse est...

AuthenticationController avec actions:

  • index () pour afficher le formulaire de connexion.
  • submit () pour gérer la soumission du formulaire.
  • logout (), explicite.

Non, l'authentification est un processus. Ne va pas comme ça.

LoginController avec des actions:

  • index () pour afficher le formulaire de connexion.
  • submit () pour gérer la soumission du formulaire.

Pareil ici. Connexion - action. Mieux vaut ne pas créer de contrôleur d'action (vous n'avez pas de modèle corrélé).

AccountController avec des actions:

  • loginGet () pour afficher le formulaire de connexion.
  • loginPost () pour gérer la soumission du formulaire de connexion.
  • logoutGet () pour gérer la déconnexion.
  • registerGet () pour afficher le formulaire d'inscription.
  • registerPost () pour gérer la soumission du formulaire.

Assez bien, mais je ne suis pas convaincu que la construction de ce contrôleur de bas niveau (le contrôleur est l'abstraction elle-même) mérite d'être apportée. Quoi qu'il en soit, la création de méthodes avec * Get ou * Post n'est pas claire.

Toute suggestion?

Oui, considérez-le:

AccountController:

  • connexion (AccountModel)
  • déconnexion (AccountModel)
  • s'inscrire (AccountModel)
  • indice()

Et un modèle connexe, ofc classe de compte. Cela vous donnera l'occasion de déplacer votre paire modèle-contrôleur ailleurs (si cela est nécessaire) et de créer un code clair (la signification de la login()méthode est évidente ). Stincking to model est vraiment célèbre surtout avec les applications CRUD et c'est peut-être un moyen pour vous.

Dawid Pura
la source
1

Les contrôleurs sont généralement créés pour une certaine ressource (une classe d'entité, une table dans la base de données), mais peuvent également être créés pour regrouper des actions responsables d'une certaine partie de l'application. Dans vos exemples, ce serait un contrôleur qui gère la sécurité de l'application:

class SecurityController
{
    // can handle both the login page display and
    // the login page submission
    login(); 

    logout();

    register();

    // optional: confirm account after registration
    confirm();

    // displays the forgot password page
    forgotPassword();

    // displays the reset password page
    // and handle the form submission
    resetPassword();
}

Remarque : ne placez pas les actions liées à la sécurité et les actions de profil utilisateur dans le même contrôleur; cela peut avoir un sens car ils sont liés à l'utilisateur, mais l'un doit gérer l'authentification et l'autre doit gérer les e-mails, les noms, etc.

Avec des contrôleurs créés pour les ressources (disons Task), vous auriez les actions CRUD habituelles :

class TasksController
{
    // usually displays a paginated list of tasks
    index();

    // displays a certain task, based on an identifier
    show(id);

    // displays page with form and
    // handles form submission for creating
    // new tasks
    create();

    // same as create(), but for changing records
    update(id);     

    // displays confirmation message
    // and handles submissions in case of confirmation
    delete()
}

Bien sûr, vous avez la possibilité d'ajouter des ressources associées au même contrôleur. Disons par exemple que vous avez l'entité Businesset que chacune a plusieurs BusinessServiceentités. Un contrôleur pour cela pourrait ressembler à ceci:

class BusinessController
{
    index();

    show(id);

    create();

    update(id);

    delete();

    // display the business services for a certain business
    listBusinessServices(businessId);

    // displays a certain business service
    showBusinessService(id);

    // create a new business service for a certain business
    createBusinessService(businessId);

    // updates a certain business service
    updateBusinessService(id);

    // deletes a certain business service
    deleteBusinessService(id);
}

Cette approche est logique lorsque les entités enfants liées ne peuvent pas exister sans l'entité parent.

Voici mes recommandations:

  • créer des contrôleurs basés sur un groupe d'opérations connexes (gérer certaines responsabilités comme la sécurité ou les opérations CRUD sur les ressources, etc.);
  • pour les contrôleurs basés sur les ressources, n'ajoutez pas d'actions inutiles (si vous n'êtes pas censé mettre à jour la ressource, n'ajoutez pas l'action de mise à jour);
  • vous pouvez ajouter des actions "personnalisées" pour simplifier les choses (par exemple, vous avez une Subscriptionentité qui a une disponibilité basée sur un nombre limité d'entrées, vous pouvez ajouter une nouvelle action au contrôleur nommé use()qui a pour seul but de soustraire une entrée de la Subscription)
  • garder les choses simples - n'encombrez pas votre contrôleur avec un grand nombre d'actions et une logique complexe, essayez de simplifier les choses en diminuant le nombre d'actions ou en créant deux contrôleurs;
  • si vous utilisez un framework axé sur MVC, suivez leurs recommandations de bonnes pratiques (si elles l'ont).

Quelques ressources à lire ici .

qretzu
la source
0

Je vois deux "forces" de conception antagonistes (qui ne sont pas exclusives aux contrôleurs):

  • cohésion - les contrôleurs doivent regrouper les actions connexes
  • simplicité - les contrôleurs doivent être aussi petits que possible pour gérer leur complexité

Du point de vue de la cohésion, les trois actions (connexion, déconnexion, enregistrement) sont liées, mais la connexion et la déconnexion bien plus que l'inscription. Ils sont liés sémantiquement (l'un est une inversion de l'autre) et utiliseront probablement les mêmes objets de service (leurs implémentations sont également cohérentes).

Mon premier instict serait de regrouper la connexion et la déconnexion dans un seul contrôleur. Mais si les implémentations du contrôleur de connexion et de déconnexion ne sont pas si simples (par exemple, la connexion a captcha, plus de méthodes d'authentification, etc.), je n'aurais aucun problème à les diviser en LoginController et LogoutController pour maintenir la simplicité. Où se situe ce seuil de complexité (quand vous devez commencer à diviser le contrôleur) est un peu personnel.

Souvenez-vous également que, quoi que vous conceviez initialement votre code, vous pouvez (et devez) le refactoriser au fur et à mesure qu'il change. Dans ce cas, il est assez typique de commencer par une conception simple (avoir un AuthenticationController) et avec le temps, vous recevrez plus d'exigences qui compliqueront le code. Une fois qu'il franchit le seuil de complexité, vous devez le refactoriser sur deux contrôleurs.

BTW, votre code suggère que vous déconnectez l'utilisateur avec la demande GET. C'est une mauvaise idée car HTTP GET devrait être nullipotent (il ne devrait pas modifier l'état de l'application).

qbd
la source
0

Voici quelques règles d'or:

  • Organisez par sujet ou sujet, le nom du contrôleur étant le nom du sujet.

  • N'oubliez pas que le nom du contrôleur apparaîtra dans l'URL, visible par vos utilisateurs, donc de préférence il devrait avoir du sens pour eux.

Dans la situation que vous mentionnez (authentification), l'équipe MVC a déjà écrit le contrôleur pour vous. Ouvrez Visual Studio 2013, puis cliquez sur

File / New / Project... 
Search installed templates for "ASP.NET MVC4 Web Application"
Choose "Internet Application" / OK.

AccountController.cs contient toutes les méthodes de gestion des comptes d'utilisateurs:

Login()
Logoff()
Register()
Disassociate()
Manage()
ExternalLogin()

Ils ont donc organisé par thème "Comptes d'utilisateurs et authentification", avec un nom de thème visible "Compte".


la source
0

Terminologie

Je crois que c'est une grosse idée fausse d'appeler une classe contenant des méthodes liées à HTTP un "contrôleur".

Le contrôleur est une méthode qui gère la requête, mais pas une classe contenant de telles méthodes . Ainsi, index(), submit(), logout()sont des contrôleurs.

La classe contenant ce type de méthodes est nommée "contrôleur" simplement parce qu'elle constitue un groupe de contrôleurs et joue un rôle d'espace de noms "de bas niveau". En langage FP (comme Haskell), ce ne serait qu'un module. Il est recommandé de conserver ces classes de "contrôleurs" aussi sans état que possible dans les langages POO, à l'exception des références aux services et à d'autres éléments à l'échelle du programme.

La réponse

Avec la terminologie triée, la question est "comment devrions-nous séparer les contrôleurs en espaces de noms / modules?" Je pense que la réponse est: les contrôleurs à l'intérieur d'un seul espace de noms / module doivent traiter le même type de données . Par exemple, UserControllertraite principalement des instances de Userclasse, mais touche parfois d' autres éléments connexes, si nécessaire.

Étant donné que login, logoutet d'autres actions de ce type concernent principalement la session, il est probablement préférable de les mettre à l'intérieur SessionController, et le indexcontrôleur, qui imprime simplement un formulaire, doit être placé dans LoginPageController, car il traite évidemment de la page de connexion. Il est un peu logique de mettre le rendu HTML et la gestion de session dans une seule classe, ce qui violerait SRP et probablement un tas d'autres bonnes pratiques.

Principe général

Lorsque vous avez du mal à décider où mettre un morceau de code, commencez par les données (et les types) avec lesquelles vous traitez.

scriptin
la source
2
Désolé, ce sont des actions, pas des contrôleurs :)
JK01
@ JK01 C'est comme ça que vous les appelez. C'est de la terminologie, tu sais. Et il existe des cadres qui appellent ces fonctions "contrôleurs" (ou "gestionnaires"), car il existe de nombreux cadres qui ne les organisent pas en classes, car les espaces de noms / modules sont déjà suffisants. Vous pouvez utiliser tous les termes que vous aimez, ce ne sont que des mots, mais je pense qu'il est préférable d'avoir moins de termes.
scriptin