PHP Service Manager
Want to write truly service based, loosely-coupled code, this library provides that.



Service Manager

Install the component

The best way to install the component is using Composer.

composer require webiny/service-manager

For additional versions of the package, visit the Packagist page.

Usage

Available service configuration parameters are:

  • Class
  • Arguments (Object & ObjectArguments)
  • Abstract
  • Calls
  • Scope

Extra parameters, for factory services, are:

  • Factory - class or service
  • Static (Optional) - defaults to TRUE, means that Method will be called statically on Factory object
  • Method - a method to call on Factory object
  • MethodArguments (Optional) - method arguments for Method

There are 2 possible types of scope:

  • container (Default) - only 1 instance of service is created, and is re-used on each subsequent request of service
  • prototype - a new instance of service is created each time a service is requested

Service definition

To register a service you need to create a service config object (using ConfigObject class) and pass it to ServiceManager

You can either create your configuration files in YAML or use plain PHP and build arrays straight from your code. If using PHP - call new ConfigObject($configArray) to build a proper config object. If using YAML - call $config = Config::getInstance()->yaml($pathToYourConfigFile);

Basic service definition takes only Class parameter:

$config = [
    'Class' => '\My\Service\Class'
];

ServiceManager::getInstance()->registerService('MyService', new ConfigObject($config));

// Now get your service
$myService = ServiceManager::getInstance()->getService('MyService');

You can group your services in logic groups, by creating an array of service definitions and then registering them as a group:

$config = [
    'MyLogger' => [
        'Class' => '\My\Service\Class'
    ],
    'MyMailer' => [
        'Class' => '\My\Mailer\Class'
    ]
];

ServiceManager::getInstance()->registerServices('MyServiceGroup', new ConfigObject($config));

// Now get your specific service
$myMailer = ServiceManager::getInstance()->getService('MyServiceGroup.MyMailer');

Constructor arguments

You can provide constructor arguments to your service class, by using Arguments parameter. Argument can be any value, including class name (it will be instantiated and passed to constructor as a PHP object) and a reference to another service (enter service reference using @ character):

$config = [
    'Class' => '\My\Service\Class'
    'Arguments' => ['FirstArgument', [1,2,3], '\This\Class\Will\Be\Instantiated', '@someOtherService']
];

In case you need to provide constructor parameters to your argument class or service, you will need to use an extended arguments syntax (here is a config example written in YAML):

Class: \My\Service\Class
Arguments:
    name: FirstArgument
    ids: [1,2,3]
    some_instance:
        Object: \This\Class\Will\Be\Instantiated
        ObjectArguments: [Name, Y-m-d]
    some_service:
        Object: @some.other.service
        ObjectArguments: [Name]

Creating service from a YAML config file

To create a ConfigObject out of your YAML file and register a new service, simply call:

$config = Config::getInstance()->yaml($pathToYourConfigFile);
ServiceManager::getInstance()->registerService('MyNewService', $config);

Service object method calls

In case you need to call some methods on your service instance, you can specify them using Calls parameter:

Class: \My\Service\Class
Arguments: [FirstArgument, [1,2,3], \This\Class\Will\Be\Instantiated, @some.other.service]
Calls:
    - [yourMethod]
    - [yourMethodWithArguments, [Arg1, 123]]
    - [yourMethodWithClassArgument, [\Some\Class\That\Will\Be\Instantiated]]
    - [yourMethodWithServiceArgument, [@some_service]]

Abstract services and parameters

Service manager also supports abstract services. When you have 2 or more services sharing similar functionality, you can extract common stuff into an abstract service. In the following example we also use Parameters. Parameters are like variables, define them once, and reuse whenever you need them.

Parameters config file:

# Definition of parameters
logger.class: \Webiny\Component\Logger\Logger
logger.driver.class: \Webiny\Component\Logger\Drivers\Webiny
logger.handler.udp.class: \Webiny\Component\Logger\Drivers\Webiny\Handlers\UDPHandler

NOTE: At the moment, parameters can not be used in ObjectArguments.

Services config file (this can also be defined in plain PHP array):

UdpHandler:
    Class: %logger.handler.udp.class%
TrayLoggerAbstract:
    Abstract: true
    Class: %logger.class%
    Calls:
      - [addHandler, [@UdpHandler]]
WebinySystem:
    Parent: @TrayLoggerAbstract
    Arguments: [System, %logger.driver.class%]
WebinyEcommerce:
    Parent: @TrayLoggerAbstract
    Arguments: [Ecommerce, %logger.driver.class%]

Now we need to register parameters and services with ServiceManager:

// Registering multiple parameters at once
$parameters = Config::getInstance()->yaml($pathToYourParametersConfigFile);
ServiceManager::getInstance()->registerParameters($parameters);

// Registering one parameter
ServiceManager::getInstance()->registerParameter('someClassName', '\Webiny\Some\Class\Name');

// Registering your services
$servicesConfig = Config::getInstance()->yaml($pathToYourServicesConfigFile);
ServiceManager::getInstance()->registerServices('Logger', new ConfigObject($servicesConfig));

In this example we defined and abstract service TrayLoggerAbstract and 2 real loggers that extend the abstract service, WebinySystem and WebinyEcommerce. These 2 loggers share same class and method calls, but have different constructor arguments.

You can also specify arguments in abstract class and later override them in the real class. Also, you can add more method calls from child service:

# Services
UdpHandler:
    Class: %logger.handler.udp.class%
TrayLoggerAbstract:
    Abstract: true
    Class: %logger.class%
    Arguments: [Default, %logger.driver.class%]
    Calls:
      - [addHandler, [@UdpHandler]]
WebinySystem:
    Parent: @TrayLoggerAbstract
    Calls:
    - [setSomething, [someParameter]]
WebinyEcommerce:
    Parent: @TrayLoggerAbstract
    Arguments: [Ecommerce, %logger.driver.class%]

In this last example, WebinySystem service will be constructed using the arguments from parent service and will also add an extra method call. WebinyEcommerce will provide it's own arguments to the parent constructor and will inherit the parent's Calls.

If you need to replace a method in Calls parameter, specify the third argument in call definition with the index of method to replace. In the following example, child method setSomething will replace the parent method at index 0, which is addHandler:

UdpHandler:
    Class: %logger.handler.udp.class%
TrayLoggerAbstract:
    Abstract: true
    Class: %logger.class%
    Arguments: [Default, %logger.driver.class%]
    Calls:
      - [addHandler, [@UdpHandler]]
WebinySystem:
    Parent: @TrayLoggerAbstract
    Calls:
    - [setSomething, [someParameter], 0]

If you want to replace all of the parent Calls, put an exclamation mark in front of the Calls key, and make it look like this - !Calls:

UdpHandler:
    Class: %logger.handler.udp.class%
TrayLoggerAbstract:
    Abstract: true
    Class: %logger.class%
    Arguments: [Default, %logger.driver.class%]
    Calls:
      - [addHandler, [@UdpHandler]]
WebinySystem:
    Parent: @TrayLoggerAbstract
    !Calls:
    - [setSomething, [someParameter]]

In this case, child service Calls will completely replace parent Calls.

Accessing services from PHP

To use ServiceManager in your code, the easiest way is to simply use ServiceManagerTrait. This will give you access to $this->service().

class YourClass{
    use ServiceManagerTrait;

    public function yourMethod(){
        $service = $this->service('YourServiceName');
    }
}

If you do need to access ServiceManager class directly, use it like this:

ServiceManager::getInstance()->getService('YourServiceName')

Accessing services by tags

You can group services by using tags and load all of related services using single call. To achieve that, you need to add tags key to your service configuration:

WebinySystem:
    Parent: @TrayLoggerAbstract
    !Calls:
    - [setSomething, [someParameter]]
    Tags: [logger]
WebinyCustom:
    Parent: @TrayLoggerAbstract
    Tags: [logger, custom_logger]

Now execute the following piece of code. The result will be an array containing two services: WebinySystem and WebinyCustom:

class YourClass{
    use ServiceManagerTrait;

    public function yourMethod(){
        $services = $this->servicesByTag('logger');
    }
}

You can also tell ServiceManager to filter the services using a given interface or a class name. It fill first fetch all services containing the requested tag and then filter them using the given class or interface name, before returning the final result set to you. This way you are sure you only get what you need and don't have to make checks yourself, resulting in a cleaner code:

class YourClass{
    use ServiceManagerTrait;

    public function yourMethod(){
        $services = $this->servicesByTag('cms_plugin', '\Your\Expected\Class\Or\Interface');
    }
}

Resources

To run unit tests, you need to use the following command:

$ cd path/to/Webiny/Component/ServiceManager/
$ composer.phar install
$ phpunit

To learn what you can do with Webiny Framework, checkout Webiny Platform - open-source content management system.

Explore Webiny Framework components:

  • Amazon currently supports implementation of Amazon S3
  • Annotations component for parsing annotations from a class, method or a property
  • Cache provides several caching libraries like Apc, Couchbase, Memcache and Redis
  • ClassLoader a PSR-0, PSR-4 and PEAR class loader
  • Config a very handy library for parsing YAML, INI, JSON and PHP configuration files
  • Crypt library for encoding, decoding and validating hashes
  • Entity an ODM layer (currently only supports MongoDB)
  • EventManager want to do event-based development, this is a library for you
  • Http library for parsing HTTP requests and creating HTTP responses
  • Image library for image manipulation
  • Logger PSR-3 log components for logging code execution in runtime
  • Mailer component for sending emails
  • Mongo MongoDB class wrapper
  • OAuth2 library for working with OAuth2, currently supports Facebook, LinkedIn and Google+
  • REST fully featured REST library with caching, security and rate control
  • Router handles defining, parsing, creating and matching url routes
  • Security provides authorization and authentication layer
  • ServiceManager want to write truly service based, loosely-coupled code, this library provides that
  • StdLib this component provides object wrappers for Arrays, Strings, Urls and DateTime data types
  • Storage storage abstraction layer that simplifies the way you work with files and directories
  • TemplateEngine provides a layer for rendering view templates and defining template plugins and manipulators
  • TwitterOAuth library for working with Twitter API using Twitter OAuth

SIGN UP FOR EARLY BETA ACCESS:

Lastest from our blog:

The Power of the Community

In the focus of recent feedback that I got, I think there is a need for me to justify some of the decisions we made while developing Webiny Framework.

Share & subscribe: