服务容器

未匹配的标注

服务容器(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))

注意,我们指定了在解析Mai类时应该返回哪个类。在这种情况下,我们希望去解析项目配置中指定的默认驱动程序。

可调用(Using a callable)

我们可以传入一个可调用对象,而不是直接传入一个值作为第二个参数。可调用的MUST有两个参数。第一个参数是我们试图解析的注释,第二个参数是容器本身。示例:

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_bindon_makeon_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)

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/masonite/4.0/ar...

译文地址:https://learnku.com/docs/masonite/4.0/ar...

上一篇 下一篇
贡献者:2
讨论数量: 0
发起讨论 只看当前版本


暂无话题~