不要与 Laravel 的新上下文库混淆,该包可用于构建多上下文多租户应用程序。大多数多租户库本质上都有一个“租户”上下文,因此如果您需要多个上下文,事情可能会变得有点麻烦。这个新包解决了这个问题。
让我们看一个例子好吗?
对于我们的示例应用程序,我们将拥有一个组织成团队的全球用户群,每个团队将有多个项目。这是许多软件即服务应用程序中相当常见的结构。
对于多租户应用程序来说,每个用户群都存在于一个租户上下文中并不罕见,但对于我们的示例应用程序,我们希望用户能够加入多个团队,所以它是全局用户群。
全球用户群与租户用户群图
作为 SaaS,团队很可能是计费实体(即席位),并且某些团队成员将被授予管理团队的权限。不过,我不会在此示例中深入探讨这些实现细节,但希望它提供一些额外的上下文。
为了保持这篇文章的简洁,我不会解释如何启动你的 Laravel 项目。已经有许多更好的资源可用,尤其是官方文档。我们假设您已经有一个 Laravel 项目,其中包含用户、团队和项目模型,并且您已准备好开始实现我们的上下文包。
安装很简单 作曲家推荐:
雷雷
这个库有一个方便的函数 context(),从 Laravel 11 开始,它与 Laravel 自己的 context 函数发生冲突。这其实并不是一个问题。您可以导入我们的函数:
雷雷
或者直接使用 Laravel 的依赖注入容器。在这篇文章中,我将假设您已导入该函数并相应地使用它。
让我们从配置我们的团队模型开始:
雷雷
团队有名称、成员和项目。在我们的应用程序中,只有团队成员才能访问该团队或其项目。
好吧,让我们看看我们的项目:
雷雷
一个项目有一个名字并且属于一个团队。
当有人访问我们的应用程序时,我们需要确定他们在哪个团队和项目中工作。为了简单起见,我们用路由参数来处理这个问题。我们还假设只有经过身份验证的用户才能访问该应用程序。
既不是团队也不是项目上下文:app.mysaas.dev
仅团队上下文:app.mysaas.dev/my-team
团队和项目上下文:app.mysaas.dev/my-team/my-project
我们的路线将如下所示:
雷雷
考虑到命名空间冲突的可能性,这是一种非常不灵活的方法,但它使示例保持简洁。在现实世界的应用程序中,您需要稍微不同地处理这个问题,也许是 anothersaas.dev/teams/my-team/projects/my-project 或 my-team.anothersas.dev/projects/my-project。
我们应该首先看看我们的AppContextMiddleware。该中间件初始化团队上下文以及项目上下文(如果设置):
雷雷
首先,我们从路线中获取团队 ID,然后忘记路线参数。一旦参数进入上下文,我们就不需要到达控制器。如果设置了项目 ID,我们也会提取它。然后,我们使用 AppResolver 传递团队 id 和项目 id(或 null)来初始化上下文:
雷雷
这里还有更多事情要做。
define() 方法负责定义正在解析的上下文。团队是必需的并且必须是 Team 模型,并且项目被接受(即可选)并且必须是 Project 模型(或 null)。
resolveTeam() 将在初始化时在内部调用。它返回 Team 或 null。如果出现空响应,ContextInitializer 将抛出 CouldNotResolveRequiredContextException。
resolveProject() 也将在初始化时在内部调用。它返回项目或 null。在这种情况下,空响应不会导致异常,因为定义不需要该项目。
解析团队和项目后,ContextInitializer 将调用可选的 checkTeam() 和 checkProject() 方法。这些方法执行完整性检查。对于 checkTeam(),我们确保经过身份验证的用户是团队的成员,对于 checkProject(),我们检查项目是否属于团队。
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.
以上是使用 honeystone/context 构建多租户应用程序的详细内容。更多信息请关注PHP中文网其他相关文章!