Skip to content

mediapills/dependency-injection

Repository files navigation

License: MIT Documentation Status Wheel Downloads GitHub Workflow Actions Status Maintainability Absolute coverage and coverage changes Requirements Status CII Best Practices

Usage

Creating a injector is a matter of creating a Container instance:

from mediapills.dependency_injection import Container

injector = Container()

As many other dependency injection containers, mediapills.dependency_injection manages two different kind of data: services and parameters.

Defining Services

A service is an object that does something as part of a larger system. Examples of services: a database connection, a templating engine, or a mailer. Almost any object can be a service.

Services are defined by anonymous functions that return an instance of an object:

# define some services
injector['session_storage'] = lambda di: (
    SessionStorage('SESSION_ID')
)

injector['session'] = lambda di: (
    Session(di['session_storage'])
)

Notice that the anonymous function has access to the current injector instance, allowing references to other services or parameters.

As objects are only created when you get them, the order of the definitions does not matter.

Using the defined services is also very easy:

# get the session object
session = injector['session']

# the above call is roughly equivalent to the following code:
# storage = SessionStorage('SESSION_ID')
# session = Session(storage)

Defining Factory Services

By default, each time you get a service, Container returns the same instance of it. If you want a different instance to be returned for all calls, wrap your anonymous function with the factory() method

injector['session'] = injector.factory(lambda di: (
    Session(di['session_storage'])
))

Now, each call to injector['session'] returns a new instance of the session.

Defining Parameters

Defining a parameter allows to ease the configuration of your container from the outside and to store global values:

# define some parameters
injector['cookie_name'] = 'SESSION_ID'
injector['session_storage_cls'] = SessionStorage

If you change the session_storage service definition like below:

injector['session_storage'] = lambda di: (
    di['session_storage_cls'](di['cookie_name'])
)

You can now easily change the cookie name by overriding the cookie_name parameter instead of redefining the service definition.

Protecting Parameters

Because Pimple sees anonymous functions as service definitions, you need to wrap anonymous functions with the protect() method to store them as parameters:

injector['random_func'] = lambda i: rand()
injector.protect('random_func')

Modifying Services after Definition

In some cases you may want to modify a service definition after it has been defined. You can use the extend() method to define additional code to be run on your service just after it is created:

injector['session_storage'] = lambda di: (
    di['session_storage_class'](di['cookie_name'])
)

def session_storage_ext(storage: Callable, di: Container):
    # Do something with base storage using di

    return storage

injector.extend('session_storage', session_storage_ext)

The first argument is the name of the service to extend, the second a function that gets access to the object instance and the container.

Fetching the Service Creation Function

When you access an object, Container automatically calls the anonymous function that you defined, which creates the service object for you. If you want to get raw access to this function, you can use the raw() method:

injector['session'] = lambda di: (
    Session(di['session_storage'])
)

sessionFunction = container.raw('session')