socialgekon.com
  • Principal
  • Personnes Et Équipes Produit
  • Design De Marque
  • Équipes Distribuées
  • Investisseurs Et Financement
La Technologie

Maintenir les cadres Slim PHP MVC avec une structure en couches

Contrôleurs et modèles Fat: un problème inévitable pour la plupart des projets à grande échelle basés sur des frameworks MVC tels que Yii et Laravel. La principale chose qui engraisse les contrôleurs et les modèles est le Enregistrement actif , une composante puissante et essentielle de ces cadres.

Le problème: les enregistrements actifs et leur violation du SRP

L'enregistrement actif est un modèle architectural, une approche d'accès aux données d'une base de données. Il a été nommé par Martin Fowler dans son livre de 2003 Modèles d'architecture d'application d'entreprise et est largement utilisé dans PHP Cadres.

Bien qu'il s'agisse d'une approche très nécessaire, le modèle d'enregistrement actif (AR) enfreint le principe de responsabilité unique (SRP) car les modèles AR:



  • Traitez les requêtes et la sauvegarde des données.
  • En savoir trop sur les autres modèles du système (à travers les relations).
  • Sont souvent directement impliqués dans la logique métier de l’application (car la mise en œuvre du stockage des données est étroitement liée à cette logique métier).

Cette violation du SRP est un bon compromis pour un développement rapide lorsque vous devez créer un prototype d'application dès que possible, mais elle est assez nuisible lorsque l'application se transforme en un projet de moyenne ou grande envergure. Les modèles «Dieu» et les gros contrôleurs sont difficiles à tester et à maintenir, et l'utilisation libre de modèles partout dans les contrôleurs conduit à d'énormes difficultés lorsque vous devez inévitablement changer la structure de la base de données.

La solution est simple: divisez la responsabilité de l'enregistrement actif en plusieurs couches et injectez des dépendances entre couches. Cette approche simplifiera également les tests car elle vous permet de simuler les couches qui ne sont pas actuellement testées.

La solution: une structure en couches pour les frameworks PHP MVC

Une application PHP MVC «grosse» a des dépendances partout, imbriquées et sujettes aux erreurs, tandis qu'une structure en couches utilise l'injection de dépendances pour garder les choses propres et claires.

Nous allons couvrir cinq couches principales:

  • La couche contrôleur
  • La couche de service
  • DTO , un sous-ensemble de la couche de service
  • Voir les décorateurs , un sous-ensemble de la couche de service
  • La couche de référentiel

Une structure PHP en couches

Pour implémenter une structure en couches, nous avons besoin d'un conteneur d'injection de dépendance , un objet qui sait comment instancier et configurer des objets. Vous n’avez pas besoin de créer une classe car le framework gère toute la magie. Considérer ce qui suit:

class SiteController extends IlluminateRoutingController { protected $userService; public function __construct(UserService $userService) { $this->userService = $userService; } public function showUserProfile(Request $request) { $user = $this->userService->getUser($request->id); return view('user.profile', compact('user')); } } class UserService { protected $userRepository; public function __construct(UserRepository $userRepository) { $this->userRepository = $userRepository; } public function getUser($id) { $user = $this->userRepository->getUserById($id); $this->userRepository->logSession($user); return $user; } } class UserRepository { protected $userModel, $logModel; public function __construct(User $user, Log $log) { $this->userModel = $user; $this->logModel = $log; } public function getUserById($id) { return $this->userModel->findOrFail($id); } public function logSession($user) { $this->logModel->user = $user->id; $this->logModel->save(); } }

Dans l'exemple ci-dessus, UserService est injecté dans SiteController, UserRepository est injecté dans UserService et les modèles AR User et Logs sont injectés dans le UserRepository classe. Ce code de conteneur est assez simple, alors parlons des couches.

La couche contrôleur

Les frameworks MVC modernes comme Laravel et Yii relèvent pour vous bon nombre des défis des contrôleurs traditionnels: la validation d'entrée et les pré-filtres sont déplacés vers une autre partie de l'application (dans Laravel, c'est dans ce qu'on appelle middleware alors qu'en Yii, il s'appelle comportement ) tandis que les règles de routage et de verbe HTTP sont gérées par le framework. Cela laisse une fonctionnalité très étroite au programmeur pour coder dans un contrôleur.

L'essence d'un contrôleur est d'obtenir une demande et de fournir les résultats. Un contrôleur ne doit contenir aucune logique métier d'application; sinon, il est difficile de réutiliser le code ou de modifier la façon dont l’application communique. Si vous devez créer une API au lieu de rendre des vues, par exemple, et que votre contrôleur ne contient aucune logique, vous changez simplement la façon dont vous renvoyez vos données et vous êtes prêt à partir.

Cette fine couche de contrôleur confond souvent les programmeurs et, comme un contrôleur est une couche par défaut et le point d'entrée le plus élevé, de nombreux développeurs continuent d'ajouter du nouveau code à leurs contrôleurs sans aucune réflexion supplémentaire sur l'architecture. En conséquence, des responsabilités excessives s'ajoutent, des responsabilités telles que:

  • Logique métier (qui rend impossible la réutilisation du code de logique métier).
  • Changements directs des états du modèle (auquel cas tout changement dans la base de données entraînerait d'énormes changements partout dans le code).
  • Logique de relation de modèle (comme les requêtes complexes, la jonction de plusieurs modèles; encore une fois, si quelque chose est changé dans la base de données ou dans la logique de relation, nous devrions le changer dans tous les contrôleurs).

Prenons un exemple de contrôleur surdimensionné:

//A bad example of a controller public function user(Request $request) { $user = User::where('id', '=', $request->id) ->leftjoin('posts', function ($join) { $join->on('posts.user_id', '=', 'user.id') ->where('posts.status', '=', Post::STATUS_APPROVED); }) ->first(); if (!empty($user)) { $user->last_login = date('Y-m-d H:i:s'); } else { $user = new User(); $user->is_new = true; $user->save(); } return view('user.index', compact('user')); }

Pourquoi cet exemple est-il mauvais? Pour de nombreuses raisons:

  • Il contient trop de logique métier.
  • Cela fonctionne directement avec l'enregistrement actif, donc si vous modifiez quelque chose dans la base de données, comme renommer le last_login champ, vous devez le changer dans tous les contrôleurs.
  • Il connaît les relations de base de données, donc si quelque chose change dans la base de données, nous devons le changer partout.
  • Il n’est pas réutilisable, ce qui entraîne la répétition du code.

Un contrôleur doit être mince; vraiment, tout ce qu'il devrait faire est de prendre une demande et de renvoyer les résultats. Voici un bon exemple:

//A good example of a controller public function user (Request $request) { $user = $this->userService->getUserById($request->id); return view('user.index', compact('user')); }

Mais où vont tous ces autres trucs? Il appartient à la couche de service .

La couche de service

La couche de service est une couche de logique métier. Ici, et seulement ici, les informations sur le flux des processus métier et l'interaction entre les modèles commerciaux doivent être situées. Il s'agit d'une couche abstraite et elle sera différente pour chaque application, mais le principe général est l'indépendance de votre source de données (la responsabilité d'un contrôleur) et le stockage des données (la responsabilité d'une couche inférieure).

C'est l'étape avec le plus de potentiel de problèmes de croissance. Souvent, un modèle d'enregistrement actif est renvoyé à un contrôleur et, par conséquent, la vue (ou dans le cas d'une réponse API, le contrôleur) doit fonctionner avec le modèle et être conscient de ses attributs et dépendances. Cela rend les choses désordonnées; si vous décidez de changer une relation ou un attribut d'un modèle Active Record, vous devez le changer partout dans toutes vos vues et contrôleurs.

Voici un exemple courant que vous pourriez rencontrer d'un modèle d'enregistrement actif utilisé dans une vue:

    @foreach($user->posts as $post)
  • {{$post->title}}
  • @endforeach

Cela semble simple, mais si je renomme le first_name champ, je dois soudainement changer toutes les vues qui utilisent le champ de ce modèle, un processus sujet aux erreurs. Le moyen le plus simple d'éviter cette énigme consiste à utiliser des objets de transfert de données, ou DTO.

Objets de transfert de données

Les données de la couche de service doivent être enveloppées dans un simple objet immuable - ce qui signifie qu'elles ne peuvent pas être modifiées après leur création - nous n'avons donc pas besoin de setters pour un DTO. En outre, la classe DTO doit être indépendante et ne pas étendre les modèles Active Record. Attention, cependant, un modèle commercial n'est pas toujours le même qu'un modèle AR.

Envisagez une application de livraison d'épicerie. Logiquement, une commande d'épicerie doit inclure des informations de livraison, mais dans la base de données, nous stockons les commandes et les lions à un utilisateur, et l'utilisateur est lié à une adresse de livraison. Dans ce cas, il existe plusieurs modèles AR, mais les couches supérieures ne devraient pas en avoir connaissance. Notre classe DTO contiendra non seulement la commande, mais également les informations de livraison et toute autre pièce qui correspond à un modèle commercial. Si nous modifions les modèles AR liés à ce modèle commercial (par exemple, nous déplaçons les informations de livraison dans la table de commande), nous modifierons uniquement le mappage des champs dans l'objet DTO, plutôt que de modifier votre utilisation des champs de modèle AR partout dans le code.

En utilisant une approche DTO, nous supprimons la tentation de changer le modèle d'enregistrement actif dans le contrôleur ou dans la vue. Deuxièmement, l'approche DTO résout le problème de la connectivité entre le stockage physique des données et la représentation logique d'un modèle d'entreprise abstrait. Si quelque chose doit être modifié au niveau de la base de données, les modifications affecteront l'objet DTO plutôt que les contrôleurs et les vues. Vous voyez un modèle?

Jetons un coup d'œil à un DTO simple:

//Example of simple DTO class. You can add any logic of conversion from an Active Record object to business model here class DTO { private $entity; public static function make($model) { return new self($model); } public function __construct($model) { $this->entity = (object) $model->toArray(); } public function __get($name) { return $this->entity->{$name}; } }

L'utilisation de notre nouveau DTO est tout aussi simple:

//usage example public function user (Request $request) { $user = $this->userService->getUserById($request->id); $user = DTO::make($user); return view('user.index', compact('user')); }

Voir les décorateurs

Pour séparer la logique de vue (comme choisir la couleur d’un bouton en fonction d’un état), il est judicieux d’utiliser une couche supplémentaire de décorateurs. UNE décorateur est un modèle de conception qui permet d'embellir un objet principal en l'enveloppant avec des méthodes personnalisées. Cela se produit généralement dans la vue avec une logique quelque peu spéciale.

Alors qu'un objet DTO peut effectuer le travail d'un décorateur, il ne fonctionne vraiment que pour les actions courantes telles que le formatage de la date. Un DTO doit représenter un modèle commercial, tandis qu'un décorateur embellit les données avec du HTML pour des pages spécifiques.

Examinons un extrait d'une icône d'état de profil utilisateur qui n'emploie pas de décorateur:

@if($user->status == AppModelsUser::STATUS_ONLINE) Online @else Offline @endif {{date('F j, Y', strtotime($user->lastOnline))}}

Bien que cet exemple soit simple, il serait facile pour un développeur de se perdre dans une logique plus compliquée. C'est là qu'intervient un décorateur pour nettoyer la lisibilité du HTML. Développons notre extrait d'icône d'état en un cours de décorateur complet:

class UserProfileDecorator { private $entity; public static function decorate($model) { return new self($model); } public function __construct($model) { $this->entity = $model; } public function __get($name) { $methodName = 'get' . $name; if (method_exists(self::class, $methodName)) { return $this->$methodName(); } else { return $this->entity->{$name}; } } public function __call($name, $arguments) { return $this->entity->$name($arguments); } public function getStatus() { if($this->entity->status == AppModelsUser::STATUS_ONLINE) { return 'Online'; } else { return 'Offline'; } } public function getLastOnline() { return date('F j, Y', strtotime($this->entity->lastOnline)); } }

L'utilisation du décorateur est simple:

public function user (Request $request) { $user = $this->userService->getUserById($request->id); $user = DTO::make($user); $user = UserProfileDecorator::decorate($user); return view('user.index', compact('user')); }

Nous pouvons désormais utiliser les attributs de modèle dans la vue sans aucune condition ni logique, et c'est beaucoup plus lisible:

{{$user->status}} {{$user->lastOnline}}

Les décorateurs peuvent également être combinés:

public function user (Request $request) { $user = $this->userService->getUserById($request->id); $user = DTO::make($user); $user = UserDecorator::decorate($user); $user = UserProfileDecorator::decorate($user); return view('user.index', compact('user')); }

Chaque décorateur fera son travail et ne décorera que sa propre partie. Cette intégration récursive de plusieurs décorateurs permet une combinaison dynamique de leurs fonctionnalités sans introduire de classes supplémentaires.

La couche de référentiel

La couche référentiel fonctionne avec la mise en œuvre concrète du stockage de données. Il est préférable d’injecter le référentiel via une interface pour plus de flexibilité et un remplacement facile. Si vous modifiez votre stockage de données, vous devez créer un nouveau référentiel qui implémente votre interface de référentiel, mais au moins vous n'avez pas à modifier les autres couches.

Le référentiel joue le rôle d'un objet de requête: il récupère les données de la base de données et conduit le travail de plusieurs modèles Active Record. Les modèles Active Record, dans ce contexte, jouent le rôle d'entités de modèle de données uniques - tout objet du système sur lequel vous souhaitez modéliser et stocker des informations. Bien que chaque entité contienne des informations, elle ne sait pas comment elles sont apparues (si elles ont été créées ou obtenues à partir de la base de données), ni comment enregistrer et modifier son propre état. La responsabilité du référentiel est de sauvegarder et / ou de mettre à jour une entité; cela permet une meilleure séparation des préoccupations en maintenant la gestion des entités dans le référentiel et en simplifiant les entités.

Voici un exemple simple de méthode de référentiel qui crée une requête en utilisant les connaissances sur la base de données et les relations Active Record:

public function getUsers() { return User::leftjoin('posts', function ($join) { $join->on('posts.user_id', '=', 'user.id') ->where('posts.status', '=', Post::STATUS_APPROVED); }) ->leftjoin('orders', 'orders.user_id', '=', 'user.id') ->where('user.status', '=', User::STATUS_ACTIVE) ->where('orders.price', '>', 100) ->orderBy('orders.date') ->with('info') ->get(); }

Rester mince avec des couches de responsabilité unique

Dans une application nouvellement créée, vous ne trouverez que des dossiers pour les contrôleurs, les modèles et les vues. Ni Yii ni Laravel n’ajoutent de couches supplémentaires dans la structure de leur exemple d’application. Simple et intuitive, même pour les novices, la structure MVC simplifie le travail avec le framework, mais il est important de comprendre que leur exemple d'application est un exemple; ce n’est pas un standard ou un style et n’impose aucune règle sur l’architecture des applications. En divisant les tâches en couches de responsabilité distinctes et uniques, nous obtenons une architecture flexible et extensible qui est facile à maintenir. Rappelles toi:

  • Entités sont des modèles de données uniques.
  • Référentiels récupérer et préparer les données.
  • La couche de service n'a que de la logique métier.
  • Contrôleurs communiquer avec toutes les sources externes telles que l'entrée utilisateur ou un service tiers.

Donc, si vous démarrez un projet complexe ou un projet qui a une chance de se développer à l'avenir, envisagez une répartition claire des responsabilités entre les couches contrôleur, service et référentiel.

Comment obtenir des clients consultants - Une entrevue avec Steve Blank

Processus Financiers

Comment obtenir des clients consultants - Une entrevue avec Steve Blank
ApeeScape acquiert Skillbridge sur le marché des talents commerciaux

ApeeScape acquiert Skillbridge sur le marché des talents commerciaux

Autre

Articles Populaires
L'appareil photo de l'iPhone X fait-il toujours la différence ?
L'appareil photo de l'iPhone X fait-il toujours la différence ?
Top 10 des erreurs les plus courantes commises par les développeurs Android: un didacticiel de programmation
Top 10 des erreurs les plus courantes commises par les développeurs Android: un didacticiel de programmation
Comment nettoyer l'objectif de l'appareil photo de l'iPhone
Comment nettoyer l'objectif de l'appareil photo de l'iPhone
Introduction à la programmation simultanée: guide du débutant
Introduction à la programmation simultanée: guide du débutant
Comment établir un budget qui dure toute l'année
Comment établir un budget qui dure toute l'année
 
Présentation de Hoodie: développement Full Stack pour les développeurs front-end
Présentation de Hoodie: développement Full Stack pour les développeurs front-end
La psychologie des dispositifs portables et des technologies portables
La psychologie des dispositifs portables et des technologies portables
Éliminer le ramasse-miettes: la méthode RAII
Éliminer le ramasse-miettes: la méthode RAII
Noyaux d'arbres: quantification de la similitude entre les données structurées en arborescence
Noyaux d'arbres: quantification de la similitude entre les données structurées en arborescence
Introduction à Apache Spark avec des exemples et des cas d'utilisation
Introduction à Apache Spark avec des exemples et des cas d'utilisation
Catégories
AgileMode De VieBack-EndLes TendancesConception MobileConseils Et OutilsÉquipes DistribuéesKpi Et AnalysesL'avenir Du TravailÉdition

© 2023 | Tous Les Droits Sont Réservés

socialgekon.com