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.
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.
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.
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)
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 ;-)
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+.