Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

PHP Service Locator (IoC pattern)

branch: master

Fetching latest commit…

Octocat-spinner-32-eaf2f5

Cannot retrieve the latest commit at this time

Octocat-spinner-32 lib
Octocat-spinner-32 tests
Octocat-spinner-32 API.md
Octocat-spinner-32 LICENSE
Octocat-spinner-32 README.md
README.md

Description

Select is a static Service Locator implementation with PHP method ovearloading. It allows to replace classes and can be used to hold components/services, identified by unique names and automatically exposed with getter methods.

Select is designed to be subclassed with a custom class name, as opposed to the common injection through constructors. To replace Select you subclass the main class. For instance: during tests you can either use a different set of definitions (suggested) or use a mocked Service Locator class implementing the same interface iSelect.

Service Locator is an Inversion of Control pattern, an alternative to Constructor Injection and Setter Injection. Each approach has pros and cons, you might want to read Inversion of Control Containers and the Dependency Injection pattern for more details on IoC, Dependency Injection and Service Locators (in the Java world).

Select does not require any configuration, it is simple to introduce the feature if required, see Select::def<*> API for details.

How to start

To start using Select you should extend the original class and set your namespace (optional), choosing a class name such as SL, IOC, Manager etc. In order to do this create a file, ie DI.php with this code:

    namespace MyApp;
    class DI extends \Select {
        const DEFAULT_NAMESPACE = 'MyApp\\';
        static protected $_namespace = self::DEFAULT_NAMESPACE;
    }

or

    class DI extends Select {
        const DEFAULT_NAMESPACE = 'MyApp_';
        static protected $_namespace = self::DEFAULT_NAMESPACE;
    }

You can also configure the included unit test case (see data provider and setUp) with the custom class name, to verify that everything works properly. You might have to create a fixture, see tests/fixtures/select\*NS.php_.

Notice that subclasses don't need to explicit any interface because Select uses method overloading to catch calls to the stored dependencies. iSelect should be used when replacing Select with a new or fake implementation.

Customising the class methods

If you intend to override methods please note that Select uses func_get_args() to fetch parameters not explicitly defined in function signatures.

To add new methods or rename existing methods, please note that Select uses __callStatic to catch def/ini/set/get<*> calls. See __callStatic documentation for details.

Inversion of control

The main idea is to separate configuration (class names) from implementation (class instantiations and static calls), avoiding hard coded class names, so that they can be replaced by third parties and during tests. Select is an implementation of a Service Locator, a Registry holding classes and services definitions.

The first step should be replacing class symbols with class identifiers:

    $mailer = new Mailer()

with:

    $mailer = Select::create('Mailer')

This allows to change the actual class used for the identifier 'Mailer'.

You might also want to use Select as a Service Locator, storing dependencies definitions in the container and retrieving them whenever you want. The previous code becomes:

    Select::defMailer('Mailer')
    $mailer = Select::getMailer()

To keep definitions and uses separated, all the definitions can be stored in a "configuration file", i.e. definitions.php, e.g.

    Select::defMailer('Mailer')
    Select::defCache('MemCache')
    Select::defHTTPClient('HTTPClientV2', array('proxy.mydomain.net:80'))
    Select::defAuthService('AuthService', array('192.168.0.1', 636))
    etc.

Dependencies defined with Select::def<*> will not instantiate until they are requested for the first time. However you can also define and instantiate at once if you like:

    $mailer = Select::iniMailer('Mailer')

and

    Select::iniDB('DBConnector', array('127.0.0.1', 3306, 'user', 'password'))->connect()
    // ...
    Select::getDB()->disconnect()

And you can also postpone a dependency definition after its creation:

    $logger = Select::create('Logger', '/tmp/app.log')
    $logger->rotate()
    Select::setLogger($logger)

Constructors, Singletons, Multitons

With regards to the constructor parameters, they can be passed to Select::def<*> and to Select::ini<*> as an array, after the class name. Select::create expects constructor's parameters inline after the class name instead. See the API documentation for details and examples.

By default all dependencies defined and/or created with Select::<def|ini|set><*> are considered singletons. See the API documentation for multitons.
Select::create always returns a new object.

Constructors are limited to a maximum of 6 parameters, if you need more you shall override Select::_create, or perhaps reduce your code complexity ;-)

Notes

Select exceptions classes are hard coded, there shouldn't be any reason to replace them. Each error type has a dedicated exception class subclassing Select_Exception.

Static calls require two lines of code, one to fetch the class name and one for the actual code:

    $class = Select::getClass('AStaticClass')
    $class::someMethod()

The cost of the abstraction should be in the order of few ms every 1000 instantiations (tested on low spec hw). Have a look at tests/speed.php to test Select on your system. Select requires PHP 5.3+.

Something went wrong with that request. Please try again.