Disons que chaque fois que je fais une opération CRUD ou modifie une relation d'une manière spécifique, je veux aussi faire autre chose. Par exemple, chaque fois que quelqu'un publie un article, je souhaite également enregistrer un élément dans une table pour analyse. Peut-être pas le meilleur exemple, mais en général il y a beaucoup de cette fonctionnalité "groupée".
Normalement, je vois ce type de logique dans les contrôleurs. Tout va bien jusqu'à ce que vous souhaitiez reproduire cette fonctionnalité dans de nombreux endroits. Lorsque vous commencez à vous lancer dans les partiels, à créer une API et à générer un contenu factice, il devient difficile de garder les éléments au sec.
Les moyens que j'ai observés pour gérer cela sont les événements, les référentiels, les bibliothèques et les ajouts aux modèles. Voici ma compréhension de chacun:
Services: C'est ici que la plupart des gens mettront probablement ce code. Mon problème principal avec les services est qu’il est parfois difficile de trouver des fonctionnalités spécifiques et que j’ai le sentiment qu’ils sont oubliés lorsque les gens se concentrent sur l’utilisation d’Eloquent. Comment saurais-je que j'ai besoin d'appeler une méthode publishPost()
dans une bibliothèque alors que je ne peux que faire $post->is_published = 1
?
La seule condition dans laquelle je vois que cela fonctionne bien est que vous utilisez UNIQUEMENT des services (et, dans l’idéal, rend Éloquent inaccessible d’une manière ou d’une autre par les contrôleurs dans leur ensemble).
En fin de compte, il semble que cela ne ferait que créer un tas de fichiers superflus si vos demandes suivent généralement la structure de votre modèle.
Repositories: D'après ce que j'ai compris, il s'agit en gros d'un service, mais il existe une interface vous permettant de basculer entre les ORM, ce dont je n'ai pas besoin.
Events: Je considère cela comme le système le plus élégant dans un sens, car vous savez que vos événements de modèle seront toujours appelés par des méthodes Eloquent. Vous pourrez ainsi écrire vos contrôleurs comme vous le feriez normalement. Je peux toutefois voir que ces problèmes deviennent désordonnés et si quelqu'un a des exemples de grands projets utilisant des événements pour un couplage critique, j'aimerais le voir.
Models: Traditionnellement, j'aurais des classes qui exécutent CRUD et gèrent également les couplages critiques. Cela a en fait facilité les choses, car vous connaissiez toutes les fonctionnalités de CRUD +, tout ce qui devait être fait avec était là.
Simple, mais dans l'architecture MVC, ce n'est pas normalement ce que je vois faire. Dans un sens, je préfère cela aux services, car il est un peu plus facile à trouver et il y a moins de fichiers à garder en mémoire. Cela peut cependant être un peu désorganisé. J'aimerais entendre les inconvénients de cette méthode et expliquer pourquoi la plupart des gens ne semblent pas le faire.
Quels sont les avantages/inconvénients de chaque méthode? Est-ce que je manque quelque chose?
Je pense que tous les modèles/architectures que vous présentez sont très utiles tant que vous suivez les principes SOLIDE .
Pour le où ajouter la logique Je pense qu'il est important de se référer au principe de responsabilité unique . De plus, ma réponse considère que vous travaillez sur un projet de taille moyenne/grande. S'il s'agit d'un projet lancez quelque chose sur une page, oubliez cette réponse et ajoutez-la aux contrôleurs ou aux modèles.
La réponse courte est: Où cela vous semble judicieux (avec services) .
La réponse longue:
Contrôleurs : Quelle est la responsabilité des contrôleurs? Bien sûr, vous pouvez mettre toute votre logique dans un contrôleur, mais est-ce la responsabilité du contrôleur? Je ne pense pas.
Pour moi, le contrôleur doit recevoir une demande et renvoyer des données et ce n’est pas le lieu de mettre des validations, d’appeler des méthodes, etc.
Modèles : Est-ce un bon endroit pour ajouter une logique comme envoyer un email de bienvenue lorsqu'un utilisateur enregistre ou met à jour le nombre de votes d'un message? Et si vous avez besoin d'envoyer le même email depuis un autre endroit dans votre code? Créez-vous une méthode statique? Que se passe-t-il si les courriels ont besoin d'informations provenant d'un autre modèle?
Je pense que le modèle devrait représenter une entité. Avec Laravel, j’utilise la classe de modèle uniquement pour ajouter des éléments tels que fillable
, guarded
, table
et les relations (c’est parce que j’utilise le modèle de référentiel, sinon le modèle également les méthodes save
, update
, find
, etc.).
Référentiels (modèle de référentiel) : Au début, j'étais très confus. Et, comme vous, je me suis dit "bon, j’utilise MySQL et c’est tout.".
Cependant, j'ai équilibré les avantages et les inconvénients de l'utilisation du modèle de référentiel et maintenant je l'utilise. Je pense que maintenant, à ce moment précis, je n’aurai besoin que d’utiliser MySQL. Mais, si dans trois ans, je dois passer à quelque chose comme MongoDB, le travail est en grande partie terminé. Tout cela au détriment d'une interface supplémentaire et d'une $app->bind(«interface», «repository»)
.
Evénements ( Observer Pattern ): Les événements sont utiles pour tout ce qui peut être lancé à n'importe quelle classe à n'importe quel moment. Pensez, par exemple, à l'envoi de notifications à un utilisateur. Lorsque vous avez besoin, vous déclenchez l'événement pour envoyer une notification à n'importe quelle classe de votre application. Ensuite, vous pouvez avoir une classe comme UserNotificationEvents
qui gère tous vos événements déclenchés pour les notifications utilisateur.
Services : Jusqu'à présent, vous avez le choix d'ajouter une logique aux contrôleurs ou aux modèles. Pour moi, il est logique d'ajouter la logique au sein de Services . Regardons les choses en face, Services est un nom de fantaisie pour les classes. Et vous pouvez avoir autant de cours que vous le souhaitez au sein de votre application.
Prenons cet exemple: Il y a peu de temps, j'ai développé quelque chose comme Google Forms. J'ai commencé avec CustomFormService
et finalement CustomFormService
, CustomFormRender
, CustomFieldService
, CustomFieldRender
, CustomAnswerService
et CustomAnswerRender
. Pourquoi? Parce que cela me semblait logique. Si vous travaillez en équipe, vous devez définir votre logique lorsque cela est logique pour l'équipe.
L'avantage d'utiliser Services vs Contrôleurs/Modèles est que vous n'êtes pas contraint par un seul contrôleur ou un seul modèle. Vous pouvez créer autant de services que nécessaire en fonction de la conception et des besoins de votre application. Ajoutez à cela l'avantage d'appeler un service dans n'importe quelle classe de votre application.
Cela va longtemps, mais je voudrais vous montrer comment j'ai structuré mon application:
app/
controllers/
MyCompany/
Composers/
Exceptions/
Models/
Observers/
Sanitizers/
ServiceProviders/
Services/
Validators/
views
(...)
J'utilise chaque dossier pour une fonction spécifique. Par exemple, le répertoire Validators
contient une classe BaseValidator
chargée du traitement de la validation, basée sur le $rules
et $messages
de validateurs spécifiques (généralement un pour chaque modèle). Je pourrais aussi facilement mettre ce code dans un service, mais il est logique pour moi d'avoir un dossier spécifique pour cela même s'il n'est utilisé que dans le service (pour le moment).
Je vous recommande de lire les articles suivants, car ils pourraient vous expliquer un peu mieux les choses:
Breaking the Mould de Dayle Rees (auteur de CodeBright): C’est là que j’ai tout mis ensemble, même si j’ai changé quelques petites choses pour répondre à mes besoins.
Découplage de votre code dans Laravel en utilisant des référentiels et des services de Chris Goosey: Ce message explique bien ce qu'est un service et le modèle de référentiel et comment ils s'harmonisent.
Les Laracasts ont également les référentiels simplifiés et responsabilité unique , qui sont de bonnes ressources avec des exemples pratiques (même si vous devez payer).
Je voulais poster une réponse à ma propre question. Je pourrais en parler pendant des jours, mais je vais essayer de publier rapidement cette information pour être sûre de la comprendre.
J'ai fini par utiliser la structure existante fournie par Laravel, ce qui signifie que j'ai conservé mes fichiers principalement en tant que modèle, vue et contrôleur. J'ai également un dossier de bibliothèques pour les composants réutilisables qui ne sont pas vraiment des modèles .
I DID N'ENROULE PAS MES MODÈLES DANS LES SERVICES/BIBLIOTHÈQUES . Toutes les raisons fournies ne m'ont pas convaincu à 100% Bien que je puisse me tromper, d’après ce que je peux voir, ils ne font que générer des tonnes de fichiers presque vides que je dois créer et basculer entre les modèles, tout en réduisant réellement les avantages d’éloquent ( particulièrement en ce qui concerne les modèles de RÉCUPÉRATION, par exemple en utilisant la pagination, les portées, etc.).
Je mets la logique métier DANS LES MODÈLES et accède directement à l'éloquent à partir de mes contrôleurs. J'utilise plusieurs approches pour m'assurer que la logique métier ne soit pas ignorée:
Répondre aux préoccupations des gens avec l'utilisation de modèles:
Remarque additionnelle: J'ai l'impression que le fait d'envelopper vos modèles dans les services équivaut à disposer d'un couteau suisse, doté de nombreux outils, et à construire un autre couteau autour de ce modèle. fait la même chose? Oui, parfois vous voudrez peut-être scotcher une lame ou vous assurer que deux lames sont utilisées ensemble ... mais il y a généralement d'autres façons de le faire ...
QUAND UTILISER DES SERVICES : Cet article énonce très bien de très bons exemples d'utilisation des services (, indice: ce n'est pas très souvent ). Il dit essentiellement lorsque votre objet utilise plusieurs modèles ou des modèles à des moments étranges de leur cycle de vie cela a du sens. http://www.justinweiss.com/articles/where-do-you-put-your-code/
Pour créer la logique entre les contrôleurs et les modèles, je crée une couche de service . Fondamentalement, ceci est mon flux pour toute action au sein de mon application:
Voici comment je le fais:
C'est la méthode d'un contrôleur pour créer quelque chose:
public function processCreateCongregation()
{
// Get input data.
$congregation = new Congregation;
$congregation->name = Input::get('name');
$congregation->address = Input::get('address');
$congregation->pm_day_of_week = Input::get('pm_day_of_week');
$pmHours = Input::get('pm_datetime_hours');
$pmMinutes = Input::get('pm_datetime_minutes');
$congregation->pm_datetime = Carbon::createFromTime($pmHours, $pmMinutes, 0);
// Delegates actual operation to service.
try
{
CongregationService::createCongregation($congregation);
$this->success(trans('messages.congregationCreated'));
return Redirect::route('congregations.list');
}
catch (ValidationException $e)
{
// Catch validation errors thrown by service operation.
return Redirect::route('congregations.create')
->withInput(Input::all())
->withErrors($e->getValidator());
}
catch (Exception $e)
{
// Catch any unexpected exception.
return $this->unexpected($e);
}
}
C'est la classe de service qui effectue la logique liée à l'opération:
public static function createCongregation(Congregation $congregation)
{
// Log the operation.
Log::info('Create congregation.', compact('congregation'));
// Validate data.
$validator = $congregation->getValidator();
if ($validator->fails())
{
throw new ValidationException($validator);
}
// Save to the database.
$congregation->created_by = Auth::user()->id;
$congregation->updated_by = Auth::user()->id;
$congregation->save();
}
Et voici mon modèle:
class Congregation extends Eloquent
{
protected $table = 'congregations';
public function getValidator()
{
$data = array(
'name' => $this->name,
'address' => $this->address,
'pm_day_of_week' => $this->pm_day_of_week,
'pm_datetime' => $this->pm_datetime,
);
$rules = array(
'name' => ['required', 'unique:congregations'],
'address' => ['required'],
'pm_day_of_week' => ['required', 'integer', 'between:0,6'],
'pm_datetime' => ['required', 'regex:/([01]?[0-9]|2[0-3]):[0-5]?[0-9]:[0-5][0-9]/'],
);
return Validator::make($data, $rules);
}
public function getDates()
{
return array_merge_recursive(parent::getDates(), array(
'pm_datetime',
'cbs_datetime',
));
}
}
Pour plus d'informations sur cette méthode, j'organise mon code pour un Laravel app: https://github.com/rmariuzzo/Pitimi
À mon avis, Laravel dispose déjà de nombreuses options pour stocker votre logique métier.
Réponse courte:
Request
de laravel pour valider automatiquement votre entrée, puis persistez les données dans la demande (créez le modèle). Étant donné que toutes les entrées d'utilisateurs sont directement disponibles en la demande, je pense qu'il est logique de le faire ici.Job
de laravel pour effectuer des tâches nécessitant des composants individuels, puis envoyez-les simplement. Je pense que Job
englobe les classes de services. Ils effectuent une tâche, telle que la logique métier.Réponse plus longue:
tilisez des réserves si nécessaire: Les référentiels sont forcément surchargés, et la plupart du temps, ils sont simplement utilisés en tant que accessor
dans le modèle. Je pense qu’ils ont certainement une utilité, mais à moins que vous ne développiez une application massive qui nécessite cette souplesse vous permet de laisser tomber laravel entièrement, éloignez-vous des dépôts, vous vous en remercierez plus tard et votre code sera beaucoup plus simple.
Demandez-vous s'il est possible que vous changiez PHP frameworks o => en un type de base de données qui laravel ne fait pas ' t soutien.
Si votre réponse est "Probablement pas", n'implémentez pas le modèle de référentiel.
En plus de ce qui précède, ne claquez pas un motif sur un superbe ORM comme Eloquent. Vous ajoutez simplement une complexité qui n'est pas requise et qui ne vous profitera pas du tout.
Pour moi, utiliser les services avec parcimonie: Les classes de services ne sont qu'un emplacement dans lequel stocker la logique applicative pour effectuer une tâche spécifique avec ses dépendances données. Laravel a ces options prédéfinies, appelées "Jobs", et elles ont beaucoup plus de flexibilité qu'une classe de service personnalisée.
Je pense que Laravel a une solution complète pour résoudre le problème de logique MVC
. Il s’agit simplement d’une question ou d’une organisation.
Exemple:
demande:
namespace App\Http\Requests;
use App\Post;
use App\Jobs\PostNotifier;
use App\Events\PostWasCreated;
use App\Http\Requests\Request;
class PostRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title' => 'required',
'description' => 'required'
];
}
/**
* Save the post.
*
* @param Post $post
*
* @return bool
*/
public function persist(Post $post)
{
if (!$post->exists) {
// If the post doesn't exist, we'll assign the
// post as created by the current user.
$post->user_id = auth()->id();
}
$post->title = $this->title;
$post->description = $this->description;
// Perform other tasks, maybe fire an event, dispatch a job.
if ($post->save()) {
// Maybe we'll fire an event here that we can catch somewhere else that
// needs to know when a post was created.
event(new PostWasCreated($post));
// Maybe we'll notify some users of the new post as well.
dispatch(new PostNotifier($post));
return true;
}
return false;
}
}
Contrôleur:
namespace App\Http\Controllers;
use App\Post;
use App\Http\Requests\PostRequest;
class PostController extends Controller
{
/**
* Creates a new post.
*
* @return string
*/
public function store(PostRequest $request)
{
if ($request->persist(new Post())) {
flash()->success('Successfully created new post!');
} else {
flash()->error('There was an issue creating a post. Please try again.');
}
return redirect()->back();
}
/**
* Updates a post.
*
* @return string
*/
public function update(PostRequest $request, $id)
{
$post = Post::findOrFail($id);
if ($request->persist($post)) {
flash()->success('Successfully updated post!');
} else {
flash()->error('There was an issue updating this post. Please try again.');
}
return redirect()->back();
}
}
Dans l'exemple ci-dessus, l'entrée de la demande est automatiquement validée et il suffit d'appeler la méthode persist et de transmettre une nouvelle publication. Je pense que la lisibilité et la maintenabilité doivent toujours primer sur les modèles de conception complexes et inutiles.
Vous pouvez ensuite utiliser exactement la même méthode de persistance pour mettre à jour les publications, car nous pouvons vérifier si la publication existe déjà ou non et effectuer une logique alternative en cas de besoin.