服务容器
服务容器(Service Container)是Masonite的特别强大的功能,我们要充分使用它。好好理解服务容器的概念,它很重要。这是一个简单的概念,但如果你不理解它背后发生了什么,就会觉得有点神奇。
入门
服务容器只是一个字典,使用键值对的方式把类(Class)加载进来。然后解析对象会去使用键、值进行检索。仅此而已。
提示:将“解析对象”想象成Masonite在说:“你需要什么?好的,这本词典里有,让我为你获取它们”
容器,它包含所有框架类以及功能,因此如果要向Masonite添加功能,只需将类(class)注册到容器中,然后即可供开发人员使用。(稍后再详细介绍)
这让Masonite非常模块化。
容器会默认解析一些对象。包括你的控制器方法(特别常见,你可能已经使用过它们)、驱动程序、中间件构造器,以及文档中指定的其他类。
想与容器交互,有三种重要的方法: bind
, make
and resolve
绑定(Bind)
为了将类绑定到容器中,只需要在app
容器中使用一个简单的bind
方法。在服务类里,如下:
from masonite.provider import ServiceProvider
from app.User import User
class UserModelProvider(ServiceProvider):
def register(self):
self.applicationlication.bind('User', User)
def boot(self):
pass
这将在容器中加载providers
字典中的键值对。调用之后的字典看起来像这样:
>>> app.providers
{'User': <class app.User.User>}
服务容器在Request
对象中可用,可以通过以下方式检索:
def show(self, request: Request):
request.app() # will return the service container
简单绑定
有时,你并不关心要绑定的对象的键是什么。例如,你可能将一个Markdown
类绑定到容器中,但实际上并不关心键的名称。这是使用简单绑定的好理由,它将键设置为对象类:
from masonite.provider import ServiceProvider
from app.User import User
class UserModelProvider(ServiceProvider):
def register(self):
self.application.simple(User)
def boot(self):
pass
检索(Make)
为了从服务容器中检索一个类,我们可以简单地使用make
方法。
>>> from app.User import User
>>> app.bind('User', User)
>>> app.make('User')
<class app.User.User>
仅此而已!这作为 IOC 容器很有用,您可以将单个类加载到容器中,并在整个项目的任何地方使用该类。
单例(Singleton)
这将在绑定时解析对象。这将允许在服务器的整个生命周期内使用相同的对象。
from masonite.provider import ServiceProvider
from app.helpers import SomeClass
class UserModelProvider(ServiceProvider):
def register(self):
self.application.singleton('SomeClass', SomeClass)
def boot(self):
pass
是否存在(Has)
你还可以使用has
方法判断容器中是否存在这个键(Key):
app.has('request')
你也可以使用in
来判断是否存在这个键.
'request' in app
收集(Collecting)
你可能希望根据键从容器中收集特定类型的对象。例如,如果我们构建一个异常处理程序,我们可能想要所有以“Exception”开头、以“Hook”结尾的对象,或者想要所有以“ExceptionHook”结尾的键。
根据键收集(Collect By Key)
我们可以很容易地根据一个键收集所有对象:
app.collect('*ExceptionHook')
这将返回一个字典,包含所有被绑定到容器中的对象。这些对象都以”ExceptionHook”结尾,如”SentryExceptionHook”或”AwesomeExceptionHook”。
反过来,收集所有以特定键开头的内容:
app.collect('Sentry*')
这将收集所有以“Sentry”开头的键,如“SentryWebhook”或“SentryExceptionHandler”。
最后,我们可能希望收集以“Sentry”开始并以“Hook”结束的内容。
app.collect('Sentry*Hook')
这将获得像“SentryExceptionHook”和“SentryHandlerHook”这样的键。
根据对象收集(Collecting By Object)
你也可以收集一个对象的所有子类。如果你想从容器中收集特定类的所有实例,你可以使用它:
from cleo import Command
...
app.collect(Command)
# Returns {'FirstCommand': <class ...>, 'AnotherCommand': ...}
解析(Resolve)
这是容器中最有用的部分。只需将对象传递到参数列表中,就可以从容器中检索对象。Masonite的某些方面已经是这样做,如控制器方法,中间件和驱动程序。
如下,在控制器的方法中这样写,表示想要获得Request
对象。所有控制器方法都由容器解析。
def show(self, request: Request):
request.user()
在这个例子中,在show方法被调用之前,Masonite将查看参数,并去容器内部查找Request对象。
Masonite知道你想要获取Request
类,会去容器中检索。Masonite将在容器中搜索Request
类,不管容器中的键是什么,进行检索、并将它注入到控制器方法中。使用依赖注入有效地创建IOC容器。可以将此视为按值获取,而不是像前面的示例那样按键获取。
挺厉害的东西,对吧?
Masonite也将解析你自定义的、特定于应用程序的类,包括那些你没有显式绑定的‘app.bind()’。
继续上面的例子,下面的代码可以开箱即用(假设相关类存在),而不需要在容器中绑定自定义类:
# 其它地方...
class MyService:
def __init__(self, some_other_dependency: SomeOtherClass):
pass
def do_something(self):
pass
# 控制器里...
def show(self, request: Request, service: MyService):
request.user()
service.do_something()
解析实例
容器的另一个强大特性是,它可以返回你在注释中写的类的实例。例如,所有Upload
驱动程序都继承自UploadContract
,它只是作为所有Upload
驱动程序的接口。许多编程范例都认为开发人员应该针对接口而不是实现进行编码,因此Masonite允许针对这个特定的用例返回类的实例。
举例:
from masonite.contracts import UploadContract
def show(self, upload: UploadContract)
upload # <class masonite.drivers.UploadDiskDriver>
注意,我们传入了一个基类而不是具体的上传类。Masonite会进入容器获取一个实例。
解析自己的代码
服务容器也可以在Masonite流程之外使用。Masonite接受一个函数或类方法,并在服务容器中找到它们,为你注入它们来解决依赖关系。
因此,您可以解析自己的任何类或函数。
from masonite.request import Request
from masonite.view import View
def randomFunction(view: View):
print(view)
def show(self, request: Request):
request.app().resolve(randomFunction) # 将打印视图对象
注意:记住不要调用它,只引用函数。服务容器需要将依赖项注入到对象中,因此它需要引用而不是可调用对象。
这将获取randomFunction
的所有参数,并从服务容器中检索它们。可能你不常用,但功能就在这儿。
使用附加参数进行解析
有时,除了在同一个参数列表中传递变量,您可能希望这样。例如,你想要有3个像这样的参数:
from masonite.request import Request
from masonite import Mail
def send_email(request: Request, mail: Mail, email):
pass
你可以将它们添加到resolve()
方法中,同时解析和传递参数:
app.resolve(send_email, 'user@email.com')
Masonite将遍历每个参数列表并解析它们,如果没有找到参数,它将从指定的其他参数中提取。这些参数可以是任意顺序的。
在Masonite流程外使用容器
如果你需要在Masonite正常流程之外使用容器,比如在命令中,那么您可以直接导入容器。
这看起来像:
from wsgi import container
from masonite import Queue
class SomeCommand:
def handle(self):
queue = container.make(Queue)
queue.push(..)
容器交换
有时,当您解析一个对象或类时,希望返回不同的值。
使用一个值
我们可以将一个简单的值作为第二个参数传递给 swap
方法,它将直接被返回,而不会去解析注释中的对象。例如,当前在解析 Mail
类时是这样的:
from masonite import Mail
def show(self, mail: Mail):
mail #== <masonite.drivers.MailSmtpDriver>
但是 Mail
类的类定义是这样的:
class Mail:
pass
它怎么知道要解析 SMTP 驱动程序呢?我们需要容器交换。容器交换很简单,它们以对象作为第一个参数,以值、可调用对象作为第二个参数。
例如,我们可能想要模拟上面的功能,在 Service Provider
的 boot
方法中这样做:
from masonite import Mail
def boot(self, mail: MailManager):
self.application.swap(Mail, manager.driver(self.application.make('MailConfig').DRIVER))
注意,我们指定了在解析 Mail
类时应该返回哪个类。在这种情况下,我们希望去解析项目配置中指定的默认驱动程序。
可调用对象(Using a callable)
我们可以传入一个可调用对象,而不是直接传入一个值作为第二个参数。可调用对象必须包含 2 个参数。第一个参数是我们试图解析的注释,第二个参数是容器本身。示例:
from masonite import Mail
from somewhere import NewObject
...
def mail_callback(obj, container):
return NewObject
...
def boot(self):
self.application.swap(Mail, mail_callback)
注意,第二个参数是一个可调用对象。这意味着当我们试图解析 Mail
类时,它将被调用。
记住:如果第二个参数是可调用的,它就会被调用。如果它是一个值,则直接返回,而不返回解析对象。
容器钩子
有时,当容器内发生事件时,我们需要运行一些代码。例如,从容器中解析Request对象时想运行一个什么函数,或者我们可能想要在将Response绑定到容器时将一些值绑定到一个View类。如果我们希望在请求解析时将用户对象绑定到请求,这对于测试来说是非常好的。
我们有三个选项:on_bind
,on_make
, on_resolve
。对于第一个选项,所需要的只是钩子要绑定的键或对象,而第二个选项是带有两个参数的函数。第一个参数是问题中的对象,第二个参数是整个容器。
代码可能看起来像这样:
from masonite.request import Request
def attribute_on_make(request_obj, container):
request_obj.attribute = 'some value'
...
container = App()
# 设置钩子
container.on_make('request', attribute_on_make)
container.bind('request', Request)
# 运行attribute_on_make函数
request = container.make('request')
request.attribute # 'some value'
注意,我们创建了接受两个值的函数,一个是正在处理的对象,另一个是容器。然后每当我们运行on_make
时,函数就会被运行。
我们也可以绑定到特定的对象而不是键:
from masonite.request import Request
# ...
# 设置钩子
container.on_make(Request, attribute_on_make)
container.bind('request', Request)
# 运行attribute_on_make函数
request = container.make('request')
request.attribute # 'some value'
然后调用相同的属性,但只要Request
对象存在于容器。注意,所有东西都是一样的,除了第6行,我们用的是对象而不是字符串。
我们可以对其他选项做同样的事情:
container.on_bind(Request, attribute_on_make)
container.on_make(Request, attribute_on_make)
container.on_resolve(Request, attribute_on_make)
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。