服务容器

未匹配的标注
本文档最新版为 4.0,旧版本可能放弃维护,推荐阅读最新版!

服务容器

服务容器

介绍

服务容器是 Masonite 的一项极其强大的功能,应在最大范围内使用。了解服务容器的概念很重要。概念很简单,但是如果不了解原理,会觉得很神奇。

入门

服务容器只是一个通过键-值对将类加载到其中的字典,然后可以通过解析对象键或值对其进行检索。

解析对象,就像 Masonite 说“您的对象需要什么?”,好,在这个字典里有这些对象,我给你找出来。

容器包含所有框架类和功能,因此向 Masonite 添加功能仅需要将类添加到容器中,供开发人员稍后使用。这通常意味着将这些类“注册”到容器 (稍后将对此进行详细介绍)。

这使得 Masonite 可以实现极高的模块化。

默认情况下,容器会解析一些对象。这些包括您的控制器方法(这是最常用的方法,到目前为止,您可能已经使用了它们)驱动程序和中间件构造函数以及文档中指定的任何其他类。

与容器交互时三个重要的方法:bindmakeresolve

绑定

将类绑定到容器中,我们只需要在 app 容器上使用 app 方法。在服务提供者的代码里,看起来像这样:

from masonite.provider import ServiceProvider
from app.User import User

class UserModelProvider(ServiceProvider):

    def register(self):
        self.app.bind('User', User)

    def boot(self):
        pass

这会将键-值对加载到容器中的 providers 词典中。调用后的字典如下所示:

>>> app.providers
{'User': <class app.User.User>}

服务容器在 Request 对象中可用,并且可以通过以下方式检索:

def show(self, request: Request):
    request.app() # 将返回服务容器

简单绑定

有时,您并不在乎所绑定对象的键是什么。例如,您可能正在将 Markdown 类绑定到容器中,但实际上并不关心键是什么。这是使用简单绑定将对象类设置为键的重要原因:

from masonite.provider import ServiceProvider
from app.User import User

class UserModelProvider(ServiceProvider):

    def register(self):
        self.app.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.app.singleton('SomeClass', SomeClass)

    def boot(self):
        pass

Has

您还可以使用 has 方法检查容器中是否存在某个键:

app.has('Request')

也可以用 in

'Request' in app

收集

您可能需要根据键从容器中收集特定种类的对象。例如,如果我们要构建异常处理程序,则可能需要所有以 "Exception" 开头并以 " Hook"结尾的对象,或者希望所有以 "ExceptionHook" 结尾的键。

通过键收集

我们可以轻松地基于一个键来收集所有对象:

app.collect('*ExceptionHook')

这将返回绑定到容器的所有对象的字典,这些对象以任何内容开头并以 "ExceptionHook" 结尾,例如 “ SentryExceptionHook” 或 "AwesomeExceptionHook"。

我们也可以做相反的事情,收集以特定键开头的所有内容:

app.collect('Sentry*')

这将收集所有以 "Sentry" 开头的键,例如 " SentryWebhook" 或 " SentryExceptionHandler" 。

最后,我们可能希望收集以 "Sentry" 开头和以 "Hook" 结尾的东西
Lastly, we may want to collect things that start with "Sentry" and end with "Hook"

app.collect('Sentry*Hook')

这将得到像 "SentryExceptionHook" 和 "SentryHandlerHook" 这类的键。

通过对象收集

您还可以收集对象的所有子类。如果要从容器中收集特定类的所有实例,可以使用此方法:

from cleo import Command
...
app.collect(Command)
# 返回 {'FirstCommand': <class ...>, 'AnotherCommand': ...}

Resolve

这是容器中最有用的部分。只需将它们传递到任何对象的参数列表中,就可以从容器中检索对象。Masonite 的某些组件将会解析,例如控制器方法,中间件和驱动程序。

例如,我们可以添加类型提示参数以获取我们要获取的 Request 类并将其放入控制器。所有控制器方法都由容器解析。 Masonite 2.1默认仅支持解析注释:

def show(self, request: Request):
    request.user()

在此示例中,在调用 show 方法之前,Masonite 将查看参数并在容器内查找 Request 对象。

Masonite 知道您正在尝试获取 Request 类,并将从容器中检索该类。无论容器中的键是什么,Masonite都会在容器中搜索 Request 类,然后将其检索并注入到 controller 方法中。通过依赖注入有效地创建 IOC 容器。可以理解为 "按值获取" 而不是像前面的示例那样"按键获取" 。

很强大,是不?

解析实例

容器的另一个强大功能是它实际上可以返回您注释的类的实例。例如,所有 Upload 驱动程序都从 UploadContract 继承,而 UploadContract 只是充当所有 Upload 驱动程序的接口。许多编程范例都说开发人员应该对接口而不是实现进行编码,因此 Masonite 允许针对此特定用例返回类的实例。

举个例子:

from masonite.contracts import UploadContract

def show(self, upload: UploadContract)
    upload # <class masonite.drivers.UploadDiskDriver>

请注意,我们传递的是合约而非上传类。 Masonite 进入了容器,并提取到了合约的一个实例的类。

解析参数

不应使用此功能,而应在上一节中使用更明确的解析形式。

从技术上讲,您仍然可以像使用 Masonite 以前的版本一样使用容器来解析参数。解析参数如下所示:

from masonite.request import Request

def show(self, request: Request):
    request.user()

尽管在2.1+版本中已将其删除,但您仍可以针对每个项目启用它。要启用它,请到您的wsgi.py 文件,并将其添加到文件顶部的App类的构造函数中:

container = App(resolve_parameters=True)

您的项目现在也将解析参数。解析参数在容器中查找键而不是类中。

解析您自己的代码

服务容器也可以在 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) # 将会打印 View 对象

记住不要调用它,而只能引用该函数。服务容器需要将依赖项注入到对象中,因此它需要引用而不是调用。

这将获取 randomFunction 的所有参数,并从服务容器中检索它们。也许您解析自己的代码用法不太常用,但是选择给你了可以这么使用。

附加参数解析

有时,在解析自己的代码的时候可能需要传递一系列参数,例如您可能具有以下三个参数:

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 驱动程序呢?这是因为我们添加了一个容器交换。容器交换很简单,它们将对象作为第一个参数,将值或可调用值作为第二个参数。

例如,我们可能想通过在服务提供者的启动方法中执行以下操作来模拟上面的功能:

from masonite import Mail

def boot(self, mail: MailManager):
    self.app.swap(Mail, manager.driver(self.app.make('MailConfig').DRIVER))

注意在任何适合我们解析Mail类时都会返回我们指定的类。在这种情况下,我们希望在项目中解析到默认驱动程序。

使用可调用对象

除了传递一个值作为参数外,我们还可以传递一个可调用对象。该可调用对象必须可接收两个参数。第一个参数是希望通过注解(annotation)解析对象,第二个为容易(container)对象自身。以下是使用可调用对象工作范例:

from masonite import Mail
from somewhere import NewObject
...

def mail_callback(obj, container):
    return NewObject
...

def boot(self):
    self.app.swap(Mail, mail_callback)

注意到这里第一个参数是可调用对象。意味着它将解析Mail类时被调用。

记住:如果第二个参数是可调用对象,它将会被调用。如果是值,它仅在解析时返回。

容器钩子

有时候我们希望在容器内网执行一些动作同时运行相应代码。例如我们也许想在从容器中获取Request对象时运行相应函数,或者我们在绑定Response给容器时同时绑定一些值给视图类。我们想要在解析request对象时绑定user对象时,这对测试目的非常有用。

我们有三个选项:on_bind, on_make, on_resolve。我们需要的第一个参数是钩子想要绑定的键或者对象,第二参数是接收两个参数的函数。该函数第一个参数是所需要的对象,第二个参数是container对象。

该代码类似如下:

from masonite.request import Request

def attribute_on_make(request_obj, container):
    request_obj.attribute = 'some value'

...

container = App()

# sets the hook
container.on_make('Request', attribute_on_make)
container.bind('Request', Request)

# runs the attribute_on_make function
request = container.make('Request')
request.attribute # 'some value'

注意上面我们创建一个包含两个参数的函数,所操作的对象以及容器。无论何时运行on_make,该函数接会执行。

我也可以绑定到指定对象而不是键:

from masonite.request import Request

...

# sets the hook
container.on_make(Request, attribute_on_make)
container.bind('Request', Request)

# runs the attribute_on_make function
request = container.make('Request')
request.attribute # 'some value'

这里在使用容器获取Request对象时获取到相同属性。注意除了第六行代码使用了对象而不是字符串有区别外,其他都是一样的。

我们可以在其他事件做相同的操作:

container.on_bind(Request, attribute_on_make)
container.on_make(Request, attribute_on_make)
container.on_resolve(Request, attribute_on_make)

严格和覆盖

默认,Masonite不会干涉你容器中覆盖对象。也就意味着你可以这么做:

app.bind('number', 1)
app.bind('number', 2)

没有报错。注意我们绑定了两次相同的key。你可以通过在App类中指定两个参数来改变这行为:

container = App(strict=True, override=False)

覆盖

如果这列override指定为False,容器中就不会覆盖值。如果你尝试绑定两次相同key,它将直接忽略。如果override是True,该值为默认值,你将允许在容器中对原有key值进行覆盖。

严格

严格模式将会在你为容器绑定相同key值时抛出异常。所以,仅设置override为False会在key重复时忽略,设置strict为True才会抛出异常。

记忆

容器记忆功能是一个非常惊人的特性。容器将会将传递的对象记忆起来,而不是每次都解析,这将带来很大开销。它会将上次对象传递,通过存放在container单独构建的一个记忆列表里。

一旦对象第一次被解析,它将会存在字典里。在第二次解析时,不是再去查找依赖,Masonite会直接从记录字典里直接注入。

这里将会加速解析速度到 10 - 15倍。是一个非常有必要的速度提升。

你可以通过在容器中给应用程序配置remembering参数为True来开启该行为。你可以在wsgi.py文件里找到:

container = App(remembering = True)

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

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

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

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

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


暂无话题~