À ne pas confondre avec la nouvelle bibliothèque de contexte de Laravel, ce package peut être utilisé pour créer des applications multi-contextes multi-locataires. La plupart des bibliothèques multi-locataires ont essentiellement un seul contexte « locataire », donc si vous avez besoin de plusieurs contextes, les choses peuvent devenir un peu délicates. Ce nouveau package résout ce problème.
Regardons un exemple, d'accord ?
Pour notre exemple d'application, nous aurons une base d'utilisateurs globale organisée en équipes et chaque équipe aura plusieurs projets. Il s'agit d'une structure assez courante dans de nombreuses applications Software as a Service.
Il n'est pas rare que les applications multi-locataires aient chaque base d'utilisateurs existant dans un contexte de locataire, mais pour notre exemple d'application, nous voulons que les utilisateurs puissent rejoindre plusieurs équipes, il s'agit donc d'une base d'utilisateurs globale.
Diagramme de la base d'utilisateurs globale et de la base d'utilisateurs locataire
En tant que SaaS, il est probable que l'équipe soit l'entité facturable (c'est-à-dire le siège) et que certains membres de l'équipe soient autorisés à gérer l'équipe. Je n’entrerai cependant pas dans les détails de la mise en œuvre dans cet exemple, mais j’espère que cela fournira un contexte supplémentaire.
Pour garder cet article concis, je n'expliquerai pas comment démarrer votre projet Laravel. Il existe déjà de nombreuses meilleures ressources disponibles pour cela, notamment la documentation officielle. supposons simplement que vous avez déjà un projet Laravel, avec des modèles d'utilisateur, d'équipe et de projet, et que vous êtes prêt à commencer à implémenter notre package de contexte.
L'installation est une simple recommandation du compositeur :
composer install honeystone/context
Cette bibliothèque a une fonction pratique, context(), qui, à partir de Laravel 11, entre en conflit avec la propre fonction contextuelle de Laravel. Ce n'est pas vraiment un problème. Vous pouvez soit importer notre fonction :
use function Honestone\Context\context;
Ou utilisez simplement le conteneur d’injection de dépendances de Laravel. Tout au long de cet article, je supposerai que vous avez importé la fonction et que vous l'utilisez en conséquence.
Commençons par configurer notre modèle d'équipe :
belongsToMany(User::class); } public function projects(): HasMany { return $this->hasMany(Project::class); } }
Une équipe a un nom, des membres et des projets. Au sein de notre application, seuls les membres d'une équipe pourront accéder à l'équipe ou à ses projets.
D'accord, regardons donc notre projet :
belongsTo(Team::class); } }
Un projet a un nom et appartient à une équipe.
Lorsque quelqu'un accède à notre application, nous devons déterminer dans quelle équipe et dans quel projet il travaille. Pour simplifier les choses, traitons cela avec les paramètres d'itinéraire. Nous supposerons également que seuls les utilisateurs authentifiés peuvent accéder à l'application.
Ni contexte d'équipe ni de projet :app.mysaas.dev
Contexte d'équipe uniquement :app.mysaas.dev/my-team
Contexte de l'équipe et du projet :app.mysaas.dev/my-team/my-project
Nos itinéraires ressembleront à ceci :
Route::middleware('auth')->group(function () { Route::get('/', DashboardController::class); Route::middleware(AppContextMiddleware::Class)->group(function () { Route::get('/{team}', TeamController::class); Route::get('/{team}/{project}', ProjectController::class); }); });
Il s'agit d'une approche très rigide, étant donné le potentiel de conflits d'espaces de noms, mais elle permet de garder l'exemple concis. Dans une application réelle, vous souhaiterez gérer cela un peu différemment, peut-être anothersaas.dev/teams/my-team/projects/my-project ou my-team.anothersas.dev/projects/my-project.
Nous devrions d'abord examiner notre AppContextMiddleware. Ce middleware initialise le contexte de l'équipe et, s'il est défini, le contexte du projet :
route('team'); $request->route()->forgetParameter('team'); $projectId = null; //if there's a project, pull that too if ($request->route()->hasParamater('project')) { $projectId = $request->route('project'); $request->route()->forgetParameter('project'); } //initialise the context context()->initialize(new AppResolver($teamId, $projectId)); } }
Pour commencer, nous récupérons l'identifiant de l'équipe de l'itinéraire, puis oublions le paramètre d'itinéraire. Nous n’avons pas besoin que le paramètre atteigne nos contrôleurs une fois qu’il est dans le contexte. Si un identifiant de projet est défini, nous le extrayons également. Nous initialisons ensuite le contexte à l'aide de notre AppResolver en passant notre identifiant d'équipe et notre identifiant de projet (ou null) :
require('team', Team::class) ->accept('project', Project::class); } public function resolveTeam(): ?Team { return Team::with('members')->find($this->teamId); } public function resolveProject(): ?Project { return $this->projectId ?: Project::with('team')->find($this->projectId); } public function checkTeam(DefinesContext $definition, Team $team): bool { return $team->members->find(context()->auth()->getUser()) !== null; } public function checkProject(DefinesContext $definition, ?Project $project): bool { return $project === null || $project->team->id === $this->teamId; } public function deserialize(array $data): self { return new static($data['team'], $data['project']); } }
Il se passe un peu plus ici.
La méthode definition() est chargée de définir le contexte à résoudre. L'équipe est obligatoire et doit être un modèle d'équipe, et le projet est accepté (c'est-à-dire facultatif) et doit être un modèle de projet (ou nul).
resolveTeam() sera appelé en interne lors de l'initialisation. Il renvoie l'équipe ou null. En cas de réponse nulle, l'exception CouldNotResolveRequiredContextException sera levée par ContextInitializer.
resolveProject() sera également appelé en interne lors de l'initialisation. Il renvoie le projet ou null. Dans ce cas, une réponse nulle n'entraînera pas d'exception car le projet n'est pas requis par la définition.
Après avoir résolu l'équipe et le projet, ContextInitializer appellera les méthodes facultatives checkTeam() et checkProject(). Ces méthodes effectuent des contrôles d'intégrité. Pour checkTeam() nous nous assurons que l'utilisateur authentifié est membre de l'équipe, et pour checkProject() nous vérifions que le projet appartient à l'équipe.
Finally, every resolver needs a deserialization() method. This method is used to reinstate a serialised context. Most notably this happens when the context is used in a queued job.
Now that our application context is set, we should use it.
As usual, we’ll keep it simple, if a little contrived. When viewing the team we want to see a list of projects. We could build our TeamController to handle this requirements like this:
projects; return view('team', compact('projects')); } }
Easy enough. The projects belonging to the current team context are passed to our view. Imagine we now need to query projects for a more specialised view. We could do this:
id) ->where('name', 'like', "%$query%") ->get(); return view('queried-projects', compact('projects')); } }
It’s getting a little fiddly now, and it’s far too easy to accidentally forget to ‘scope’ the query by team. We can solve this using the BelongsToContext trait on our Project model:
belongsTo(Team::class); } }
All project queries will now be scooped by the team context and the current Team model will be automatically injected into new Project models.
Let’s simplify that controller:
get(); return view('queried-projects', compact('projects')); } }
From here onwards, you’re just building your application. The context is easily at hand, your queries are scoped and queued jobs will automagically have access to the same context from which they were dispatched.
Not all context related problems are solved though. You’ll probably want to create some validation macros to give your validation rules a little context, and don’t forget manual queries will not have the context automatically applied.
If you’re planning to use this package in your next project, we’d love to hear from you. Feedback and contribution is always welcome.
You can checkout the GitHub repository for additional documentation. If you find our package useful, please drop a star.
Until next time..
This article was originally posted to the Honeystone Blog. If you like our articles, consider checking our more of our content over there.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!