
Originally Posted by
Selkirk
Forgive me if I misunderstand the previous conversation regarding DI::get(...) versus $di->get(...). I don't think i've ever used the static method call singleton where I haven't later regretted it. How about $this->get(...) instead?
Unfortunately, as Douglas explained, the "$this" scope is not available from within the function, although theres the "$di" parameter in case you want to make a traditional-style method call. Either way you are bound to either the name of the class or the name of the parameter, so it's kinda six on the one hand or a half a dozen on the other. I favor this style:
PHP Code:
DI::register(
'store',
'return new MyStore(DI::get("gateway"));');
My reasoning behind it being that if you ever have to change the name of the singleton class, all the changes are centralized on the same line.

Originally Posted by
sweatje
Now one might argue that a singleton DI containter with a "clear()" method would be enough, but I somehow feel safer and more confident of less potential test interference with separate instances.
(sorry that it took me so long to reply to that, Jason, this thread is like a part time job
)
As Jason and others have pointed out, it might be a good idea for some to allow separate instances of the container/injector to be created, and do away with the singleton idea. Jason gives the example of unit testing, where you obviously want a set of mock objects that are not quite the same "services" you define in other parts of your application. For an instance, within your test class, you may want to use the container to retrieve an instance of the "Test" database connector as opposed to the live ones in order to do your testing.
I know that I am in the minority in this opinion, but, personally, I would probably tackle the issue from a different angle, while still using a singleton registry. I would either do something like this in my configuration script:
PHP Code:
DI::register(
'database',
'return new DbDriver("mysql", "host_prod", "username1", "password1");');
DI::register(
'test_database',
'return new DbDriver("mysql", "host_test", "username2", "password2");');
DI::register(
'order',
'return new MyOrder(DI::get("database"));');
DI::register(
'test_order',
'return new MyOrder(DI::get("test_database"));');
Which allows you to replace any aspect of your registry to inject it with mock objects. At the same time you get to see at a glance and from one single place what each "service" or "component" is made up of.
Additionally, this is also an aspect of flexibility that other DI implementations do not have: to allow for different named services that may share the same interface dependencies, but do not neccesarily have to be composed of the same classes. Unless I am mistaken, because of its reliance on interfaces, Pico does not allow you to create two different named components that share the same interface. Since interfaces are the "lookup key" within pico, you can only get either the one or the other. Personally, I think this is very limiting, specially for testing purposes. There are ways around it, as seen here in this portion of Jason's example:
PHP Code:
$pico = new DefaultPicoContainer;
ApplicationController::registerComponents($pico);
$pico->unregisterComponent('Post');
$pico->registerComponent(new InstanceComponentAdapter($post, 'Post'));
$this->assertIdentical($post, $pico->getComponentInstance('Post'));
$pico->unregisterComponent($this->actionClass);
But then you have to mess with unregistering and re-registering components, because Pico limits you to "one interface per service". I would rather have the control over how many services I want to define which follow the same interface.
The second option you have, if you would rather not mess with the central registry in order to set up your tests, would be to redefine them inside your test class like this:
PHP Code:
DI::register(
'order',
'return new MyOrder(DbDriver("mysql", "host_test", "username2", "password2"));');
(a little more like the approach you currently use in your test, Jason)
From this point forward in your script, you know that whenever you retrieve the named service/component "order", it will be a mock one. I would even argue that this method allows you more flexibility during testing in that not every single class that you are testing needs to be extracted into a mock, you can inject the mocks at any point in the dependency chain, where it matters. I don't need a MockOrder class if all that is different about it is the database driver that I pass to it in order to run my tests.
My example is probably flawed in a dozen different ways, since I am no unit testing expert (I am falling way behind the majority on these forums) but it shows what I think would be a straightforward approach to modifying the dependency registry in order to carry out your tests.
Bookmarks