Skip to content

marek-sterzik/di

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sterzik/di

This is a simple dependency-injection library. The goals of this library are:

  • easy to use
  • project independent
  • compatible with symfony-like services
  • designed to cover basic functions of the symfony DI component
  • intended for microservices, where the full-featured DI component is too expensive
  • easily being integrated in existing projects
  • support symfony-like autowiring feature
  • handle even more complicated scenarios including circular references
  • configuration is not compatible with the symfony DI component

Installation

composer require sterzik/di

Basic usage

use Sterzik\DI\DI;

// get global DI container
$di = DI::instance();

// instantiate a service:
$service = $di->get(MyServiceClass::class);

// test if a service is available
$myServiceAvailable = $di->has(MyServiceClass::class);

In this simplest scenario (without any external configuration) this holds:

  • Service names correspond to class names
  • Dependencies are resolved by autowiring driven by the specified type of the constructor argument.
  • Only objects may be autowired. Builtin types or non-typed arguments are never autowired.

custom DI container

use Sterzik\DI\DI;

// instantiate a separate custom container
$container = new DI();

// instantiate a service
$service = $di->get(MyServiceClass::class);

Using configuration in a DI container

In general there are two options how to configure a DI container. You may either pass an configuration array of service definitions or you may pass a php configuration file which returns the service definitions in the same format. The configuration is therefore either an array (direct passing of the service definitions) or a php filename (indirect passing of the service definitions) which return value will be the same service definitions. The filename may be either specified as an absolute path or a relative path, which will be resolved relatively to the project root (the directory where composer.json is present).

Configuring the global DI container

Without any setting, the global DI container (instantiated as DI::instance()) uses the config file config/services.php if present, or no configuration if the file is missing. There are 4 options, how to pass some another custom configuration:

  1. define the constant DI_SERVICE_DEFINITIONS which must be defined before the first call of DI::instance().
  2. Call DI::setServiceDefinitions($filename) with either relative or absolute file path before the first call of DI::instance() (calling DI::setServiceDefinitions() will override the DI_SERVICE_DEFINITIONS if both is present)
  3. Call DI::setServiceDefinitions($configurationArray) with an explicit configuration array before the first call of DI::instance().
  4. Use your own custom container storage based on a custom container.

Examples:

    // take configuration from $projectRoot/service-config.php
    define("DI_SERVICE_DEFINITIONS", "service-config.php");
    $di = DI::instance();
    // take configuration from /app/root/service-config.php
    DI::setServiceDefinitions("/app/root/service-config.php");
    $di = DI::instance();
    // set explicit service definitions (see later how service definitions look like)
    $config = [
        ...
    ];
    DI::setServiceDefinitions($config);
    $di = DI::instance();

Configure custom DI container

To configure a custom DI container, just pass the configuration (or config file name) to the constructor of the DI object:

    $di = new DI("config/services.php");
    $config = [
        ...
    ];
    $di = new DI($config);

Service names

A service name may be any string not starting or ending with a backslash (\). If the service name starts with a backslash, it will be stripped automatically. The service container is designed in a way to support best cases when a service name corresponds to the class name, but this rule is not mandatory. The only mandatory rules for service names are:

  • Don't start any service name with a backslash. Leading backslashes will be stripped.
  • Don't end any service name with a backslash. Trailing backslashes correspond to service prefixes instead of direct services.

Loading service definitions

Multiple service definitions play a role when instantiating a service. Let say, we want to instantiate a service Some\Cool\Service. In this case, these service definitions will be loaded (in the given order):

  • The global "unchangable" service definition.
  • The global service definition being always under the \ key if present.
  • The service definition for the prefix Some\ if present.
  • The service definition for the prefix Some\Cool\ if present.
  • The service definition for the service itself Some\Cool\Service if present.

All these service definitions are cascaded and the next service definition overrides the previous one.

Service definitions

These data types are valid service definitions:

  • functions (callables) taking one argument being the Sterzik\DI\ServiceBuilder object (preferred way how to build services). The return value of the function will decide how the service will be built (see later)
  • arrays (not yet implemented, reserved for future usage)
  • any other objects (in this case the service will be resolved "statically")

A service may be any php value. It does need to be necessarily an object, but having services as objects is the most common practice.

When the service definition is a callable, this holds for the return value:

  • if the return value is the builder itself, the builder will be responsible for building the service.
  • if the return value is null, it is the same as if the builder would be returned.
  • if any other value is returned, it will become the service

Examples:

use Sterzik\DI\DI;

$config = [
    "service1" => fn($builder) => $builder->setClass(MyService1Class::class), // callable
    "service2" => ["some" => "configuration"],                                // array
    "service3" => new StaticServiceClass(),                                   // static service resolving
];

$di = new DI($config);

// lead to: new MyService1Class()
$service1 = $di->get("service1");

// not yet implemented, leads to Sterzik\DI\Exception\NotImplementedException exception
$service2 = $di->get("service2");

// leads to the static instance of StaticServiceInstance class passed to the configuration
$service3 = $di->get("service3");
use Sterzik\DI\DI;

$config = [
    "service1" => function ($builder) { return $builder->setClass(MyService1Class::class); }, // returns builder
    "service2" => function ($builder) { $builder->setClass(MyService2Class::class); },        // returns null
    "service3" => function ($builder) { return new StaticServiceClass(); },                   // returns anything else
];

$di = new DI($config);

// lead to: new MyService1Class()
$service1 = $di->get("service1");

// lead to: new MyService2Class() (same as in the case of service1)
$service2 = $di->get("service2");

// leads to the static instance of StaticServiceInstance class created in the callable service definition of service3
$service3 = $di->get("service3");

The builder

The builder passed to the callable service definition contains many methods which may control the build process of the service. All setters return the builder itself and therefore setters may be chained.

setClass($class)

Sets the class of the built object.

Example:

use Sterzik\DI\DI;

$config = [
    "service" => function ($builder) { return $builder->setClass(MyServiceClass::class); },
];

$di = new DI($config);

$service = $di->get("service");

setArgument($argument, $value)

Sets one constructor argument. Argument may be specified either as an integer (position index) or a string (argument name).

Example:

use Sterzik\DI\DI;

$config = [
    MyServiceClass::class => fn($builder) => $builder->setArgument(0, "FirstArgument")->setArgument("argument2", "SecondArgument"),
];

$di = new DI($config);

$service = $di->get("service");

setArguments($arguments)

Set multiple constructor arguments given in the array $arguments.

Example:

use Sterzik\DI\DI;

$config = [
    MyServiceClass::class => fn($builder) => $builder->setArguments([0 => "FirstArgument", "argument2" => "SecondArgument"]),
];

$di = new DI($config);

$service = $di->get("service");

setFactory($factory)

Use the callable $factory instead of calling the constructor. Arguments set by setArgument() or setArguments() are passed to the factory callable if set.

Example:

use Sterzik\DI\DI;

$factory = function (string $argument) {
    return new Service($argument);
}

$config = [
    MyServiceClass::class => fn($builder) => $builder->setFactory($factory)->setArgument("argument", "someValue"),
];

$di = new DI($config);

$service = $di->get("service");

setAutowire($autowire)

Enable or disable the autowiring functionality. If autowire is enabled (default state) arguments of the constructor or the factory not explicitely defined will be autowired to services using the defined type of the argument.

Example:

use Sterzik\DI\DI;

// globally disable autowiring
$config = [
    '\\' => fn($builder) => $builder->setAutowire(false),
];

setRequireExplicitClass($requireExplicitClass)

Enable or disable the automatic class resolving. By default, if the class is not specified, the service name is used as the class. If this function is enabled, classes must be explicitely specified for each service.

Example:

use Sterzik\DI\DI;

// globally require explicit class
$config = [
    '\\' => fn($builder) => $builder->setRequireExplicitClass(true),
];

call($method, ...$arguments)

Call a method $method of the service after creation. The service must be an object if you want to use this feature.

Example:

use Sterzik\DI\DI;

$config = [
    MyServiceClass::class => fn($builder) => $builder->call("setupUrl", $url),
];

$di = new DI($config);

// after creation of MyServiceClass instance, the method setupUrl($url) will be called.
$service = $di->get("service");

callArgs($method, $arguments, $autowire = null)

Same as call() but arguments are passed as a single argument instead of using a variadic argument. The $autowire argument specifies, if autowiring may be used for resolving method arguments. Possible values:

  • true - do use autowiring in this method
  • false - dont use autowiring in this method
  • null - use the autowire setting valid for the constructor

Example:

use Sterzik\DI\DI;

$config = [
    MyServiceClass::class => fn($builder) => $builder->callArgs("setupUrl", [$url], false),
];

$di = new DI($config);

// after creation of MyServiceClass instance, the method setupUrl($url) will be called.
$service = $di->get("service");

setService($service)

Explicitely set the service. It has the same effect as returning the service in the service definition callback.

Example:

use Sterzik\DI\DI;

$service = new Service();

$config = [
    "service1" => fn($builder) => $service,
    "service2" => fn($builder) => $builder->setService($service),
];

$di = new DI($config);

$service1 = $di->get("service1");
$service2 = $di->get("service2");

// both, $service1 and $service2 both resolve to the same instance of $service

get($serviceName)

get the service of the given service name from the DI container.

Example:

use Sterzik\DI\DI;

$config = [
    "service" => fn($builder) => $builder->setClass(SomeClass::class)->setArgument("subService", $builder->get("subservice")),
    "subservice" => fn($builder) => $builder->setClass(SubserviceClass::class),
];

$di = new DI($config);

// service "subservice" will be wired to the constructor argument $subService of class SomeClass
$service = $di->get("service");

has($serviceName)

Test if the service of the given service name does exist in the DI container.

About

a simple DI service for PHP

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors