SitePoint Sponsor

User Tag List

Page 1 of 3 123 LastLast
Results 1 to 25 of 54
  1. #1
    SitePoint Guru
    Join Date
    Nov 2002
    Posts
    841
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Creating a Dependency Injection Container

    I've been studying dependency injection for a while. I think I've finally collected enough data points to begin constructing my own dependency injection container.

    I'm going to attempt to do this step by step in this thread.

    My first checkin defines a simple interfaces for the "new instance" capability by using the __call magic method. The function name corresponds to the class name. So, to create class Foo, with a parameter of 'hello', you can use:
    PHP Code:
    $registry = new WactRegistry();
    $obj $registry->Foo("hello"); 
    This is the equivalent of
    PHP Code:
    $obj = new Foo("hello"); 
    So what good is that? Well, if you leave off required parameters, the container will attempt to instantiate the necessary objects as best it can.
    PHP Code:
    class Cache {}
    class 
    FeedFetcher {
        function 
    __construct(Cache $cache) {}
    }

    $registry = new WactRegistry();
    $obj $registry->FeedFetcher(); // a Cache is automatically created and passed 
    Aha! Our first meager feature.

    The Code:
    registry.inc.php
    registry.test.php

    I've been kind of busy lately, having only an hour or two each night to work on this. I suspect (hope and fear) that discussion in this thread may outpace me.

  2. #2
    SitePoint Addict
    Join Date
    Apr 2004
    Location
    Melbourne
    Posts
    362
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks to the prompting of some members I finally got around to cleaning up a basic injector I built based on my experience with Grails. Essentially it reads 'services' from a location as a baseline registry (as well as manual registration), then uses reflection to interrogate objects that bind dependencies into them. It's 'limited' in that those objects that get injected need to be declared public, but the services are publicly accessible through the registery anyway, so no biggy. http://mikenovember.com/2008/another...ency-injector/ if you're interested, without trying to derail the thread too much .

  3. #3
    SitePoint Addict
    Join Date
    Sep 2006
    Posts
    232
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Nice and simple, although it's very similar to other examples I saw on this forum, the __call method was a nice touch. It's clean and simple.

    Question: Doesn't it result in a more expensive injection, because you are using reflection + an overloading method?

  4. #4
    simple tester McGruff's Avatar
    Join Date
    Sep 2003
    Location
    Glasgow
    Posts
    1,690
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    How does "WactAssembler" grab you? WactInjector? WactWiring? I tend to think of a registry as a simple box to stuff things in rather than an object which knows how to wire up the object graph, manage lifecycle, etc.

    Next feature?

    PHP Code:

    // fixture:
    interface Foo {
        function 
    a();
    }
    class 
    Bar implements Foo {

        function 
    a() {
        }
    }
        
    // test case:

        
    function setUp() {
            
    $this->assembler = new WactAssembler();
        }
        function 
    testCanInstantiateByInterface() {
            
    $this->assembler->register('Bar');
            
    $this->assertIsA($this->assembler->Foo(), 'Bar');
        }


  5. #5
    SitePoint Guru
    Join Date
    Nov 2002
    Posts
    841
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Tanus: I'll take a look at it tonight.

    phpimpact:
    Thanks on the __call. I don't think a typical php program will actually create a lot of object this way. Perhaps optimizations will be required later?

    McGruff:

    I'm open to renaming, but not right now.

    Actually, my next step was probably going to be some boring test cases for optional parameters and what happens if you try to create a class that doesn't exist.

    In the testCanInstantiateByInterface, adding a register method may not be the best thing, because I've used the "methods" axis of the class as the new instance call. (This was intentional as both a convenience syntax and to limit the complexity of the interface.) I'm not ready for that part of the interface where we define components yet.

    So, my question is, in the absence of the register statement, how and should the container satisfy the request for ->Foo() in your example?

  6. #6
    SitePoint Addict
    Join Date
    Sep 2006
    Posts
    232
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    A quick question abut test cases. I've been having a discussion lately about this. Isn't that a "test script" instead of a "test case"? For me a use case becomes a test case, and then a test case finally becomes test script, right? Or wrong....?

  7. #7
    simple tester McGruff's Avatar
    Join Date
    Sep 2003
    Location
    Glasgow
    Posts
    1,690
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Selkirk View Post
    in the absence of the register statement, how and should the container satisfy the request for ->Foo() in your example?
    I think it definitely should. I'd use a DI container to provide an easy way to tweak the behaviour of an app. There'd be a "well-known" configuration file, or class somewhere, which sets up the container to use the chosen classes (or holds the information required to do so).

    With a register/etc method ruled out, WactRegistry could maybe read some kind of configuration file - YAML, XML, or whatever. Maybe something new designed for the purpose.

    That keeps the WactRegistry interface simpler - on the other hand you have a configuration file format to learn. I haven't got a huge amount of experience using DI containers but I think I might prefer the $injector->register(...) style. They both look pretty similar when you want to pull the setup out to a single configuration file but maybe doing it in php is a little more flexible at other times if you just want to stick a couple of register() steps in a class method without having to create a separate YAML/etc file.

    Maybe WactRegistry could do a simple check for a class implementing an interface (abstract class, etc) in the list of declared classes? A bunch of include calls would be the configuration (or autoload could try to hunt them down - could be slow ). This might be limiting if you need several different classes from the same inheritance tree so I'm not sure if it's really worth considering.

    PS: we can come back to this later if I've jumped too far ahead

  8. #8
    simple tester McGruff's Avatar
    Join Date
    Sep 2003
    Location
    Glasgow
    Posts
    1,690
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by phpimpact View Post
    A quick question abut test cases. I've been having a discussion lately about this. Isn't that a "test script" instead of a "test case"? For me a use case becomes a test case, and then a test case finally becomes test script, right? Or wrong....?
    I missed out a line:

    PHP Code:
    class WhenConfiguringTheContainer extends UnitTestCase {

        function 
    setUp() {
            ...
            ...
        }
        function 
    testCanInstantiateByInterface() {
            ...
            ...
        }

    In SimpleTest, the classes all extend a UnitTestCase. Then there are test methods, and individual assertions in test methods. I'm actually not sure which of these it would be correct to refer to as a "test case".

    I'd see a script as the actual executable bit of code. Test runners might run a single method, a whole class, or a whole suite of tests.

  9. #9
    SitePoint Member
    Join Date
    Jul 2004
    Location
    Budapest, Hungary
    Posts
    24
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    The usage of __call is clever indeed. Have you considered any prefixes here?
    PHP Code:
    $obj $registry->FeedFetcher(); // isn't too convenient to read
    $obj $registry->getFeedFetcher(); // seems to be more appropriate but perhaps overly generic
    $obj $registry->provideFeedFetcher();
    $obj $registry->instantiateFeedFetcher(); // too long and subsequent calls don't necessarily result in instantiation 
    I'd prefer to see a prefix there to improve readability and to reflect the function nature of the call.

    Given there's a prefix, you can easily make the distinction between instantiation and other calls, such as register(), so the interface still provides the same simplicity while allowing the free extension of the API.

    Regarding optional parameters, would it be worth considering a way to configure their injection too?

  10. #10
    SitePoint Guru
    Join Date
    Nov 2002
    Posts
    841
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I think there has been a misunderstanding. I didn't mean to say that there would be no way to configure the container to provide a specific answer to a request for Foo(). I just meant that configuration wouldn't take the form of a method and might have a different set of parameters.

    So, I think the question remains. In the absence of configuration information, when the container is asked to create a class that implements an interface or an abstract class, what should it do?

    Throw an exception?

    Search for a concrete class that can match the request? What if there is more than one? Might this be hard to follow and debug?

  11. #11
    SitePoint Guru
    Join Date
    Nov 2002
    Posts
    841
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I hadn't though of prefixes. I think when the configuration part goes in, there will be a way to specify that a value should be injected into an optional parameter. There is just not a way to do that in the constructor style interface.

  12. #12
    SitePoint Guru
    Join Date
    Nov 2002
    Posts
    841
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Ok, here is tonights installment. I've added a singleton capabilty.

    You can fetch a singleton with the property notation.

    PHP Code:
    $value $registry->className 
    This is equivelent to
    PHP Code:
    $value $registry->className(); 
    except that it will always return the same object in subsequent calls.

    You can also set values
    PHP Code:
    $registry->foo = new StdClass();
    $registry->bar "any data"
    If it is necessary to inject a value and a singleton is available, the singleton will be injected. Otherwise, a new instance will be created (as in the last version). This newly created instance will not become a singleton. Is this the best behavior?

    I ran into what appears to be a SimpleTest bug in testing this. The following assertion fails.
    PHP Code:
    $this->assertNotIdentical(new StdClass(), new StdClass()); 
    Here is the current code:
    registry.inc.php
    registry.test.php

    Next, I think __call is getting to be a bit complicated. I think some refactoring may be in order.

  13. #13
    simple tester McGruff's Avatar
    Join Date
    Sep 2003
    Location
    Glasgow
    Posts
    1,690
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Selkirk View Post
    So, I think the question remains. In the absence of configuration information, when the container is asked to create a class that implements an interface or an abstract class, what should it do?
    Exception, I think, for the reasons mentioned.

    Sorry, but I didn't like this: $value = $registry->className It's not intuitively obvious what the difference is between that and $value = $registry->className()

    If you maintain the method embargo, the only other option would be to pass an extra parameter to __call.

    I think the behaviour for instantiating a singleton's dependencies is right. These don't necessarily have to be singletons just because a client is.

    You can also set values
    $registry->foo = new StdClass();
    $registry->bar = "any data";
    Two different meanings for $registry->foo?

  14. #14
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by McGruff View Post
    Sorry, but I didn't like this: $value = $registry->className It's not intuitively obvious what the difference is between that and $value = $registry->className()
    Agreed. I prefer the following interface:
    PHP Code:
    interface Container {
      
    // returns transient instance
      
    function create($class_name);
      
    // returns shared instance
      
    function get($class_name);


  15. #15
    SitePoint Guru
    Join Date
    Nov 2002
    Posts
    841
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Please elaborate.

    So you don't like that the following notation creates a new instance on first invocation... (What should it do instead? Error? Null?)
    PHP Code:
    $value $registry->ClassName
    Or you don't like that the __get notation is too similar to the __call notation:
    PHP Code:
    $value $registry->ClassName(); 
    In this case, which notation would you drop?

    Or is it that I've used the "class name space" for the __get notation?

    Or is it that I've used the same "name space" for both axis, __get and __call?

    Or something else?

  16. #16
    ********* Victim lastcraft's Avatar
    Join Date
    Apr 2003
    Location
    London
    Posts
    2,423
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Hi.

    How about...?
    PHP Code:
    $instance $repository->create->MyClass($parameters);
    $only_instance $repository->singleton->MyClass($parameters); 
    Means that other kinds of factories could be added to the DI container.

    Regarding selecting the appropriate implementation when handed an interface...hm...tricky without an explicit registration. If there is only one class that fulfils the interface, then I could not recommend just instantiating it. It means that app configuration would be set by file includes, which kinda' sucks. And I wouldn't want to try to figure out the interactions of this with autoload. If there are choices, then it should be up to the user anyway, so just throw regardless.

    Which brings it back to how you do registration. YAML? XML? PHP?

    I ducked this in Phemto of course, going for an explicit register() and selecting the most recent registered class (or was it the most specific...can't remember?). Besides, I wanted to make the implementation choices, but not the wiring, as visible as possible in the application.

    Personally I don't like configuration files .

    yours, Marcus
    Marcus Baker
    Testing: SimpleTest, Cgreen, Fakemail
    Other: Phemto dependency injector
    Books: PHP in Action, 97 things

  17. #17
    simple tester McGruff's Avatar
    Join Date
    Sep 2003
    Location
    Glasgow
    Posts
    1,690
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Selkirk View Post
    Please elaborate.
    It's simply that the difference isn't self-explanatory in contrast with something like getSingleton and getNewInstance (not that I'm suggesting those as alternatives).

    Now that I think about it, lifecycle choices possibly belong in a separate WactRegistry configuration stage. Clients which ask the registry for objects don't (I think) need to know about lifecycle. This seems like an injector/registry responsibility.

    Actually I do feel a bit uneasy about direct property access in general. $foo->bar feels like rummaging around in someone's private underwear drawer. That's a whole other issue though...

  18. #18
    SitePoint Guru
    Join Date
    Nov 2002
    Posts
    841
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Interesting feedback.

    I'm willing to abandon the method = class assumption, or at least weaken it to Norbert's callClass naming convention. However, I am unlikely to give up on the property notation as the primary technique for retrieving values.

    I might be convinced that a property that hasn't been assigned a value should raise an exception, rather than creating a new instance. As you can see from tonight's code, making that change to the protected getSingleton method would also remove the ability to create instances of unregistered classes while satisfying the dependencies for another object.

    In tonight's code, by having provideRequiredDependency call getSingleton, I've created a preference for singletons in satisfying required dependencies. The container will never create more instances of objects than necessary to satisfy its requirements.

    As far as config files go, I've been trying to avoid them for a couple years. I've finally come to the conclusion that any non-trivial application will grow one. So, you might as well have good support for them.

    I plan to allow both a PHP interface for setting configuration information (beyond the current __set) as well as allowing other non-code formats.

    But that's for later.

    Tonight's code is primarily a refactoring of the overly large __call method into smaller methods.

    Code:
    registry.inc.php
    registry.test.php

  19. #19
    SitePoint Guru
    Join Date
    Nov 2002
    Posts
    841
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Progress is slow. Today I extracted yesterday's refactorings into a separate class, WactRegistryFactory. Now, this class can gain its own independent test cases. The new class also needs some work. You can also see the beginnings of how the WactRegistry will handle registrations. No test case changes today. (A true refactoring.)

    registry.inc.php

  20. #20
    SitePoint Addict
    Join Date
    Sep 2006
    Posts
    232
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Hi Jeff, good stuff, the factory class was an inevitable thing.

    Now, what you have there is a local registry, that acts as the main container for all your objects and dependencies. Does that mean that you are discarding the possibility of having a global registry in the future? If that's not the case, shouldn't WactRegistry be called WactDi. And WactDi use WactDiStorage as the local storage container.

    So, you end up having something like this:

    PHP Code:
    class WactDi {
    }
    class 
    WactDiFactory {
    }
    class 
    WactDiStorage {

    I think that Registry should not be involved in the creation of objects and should only act as a container. I would leave that to the object responsable of injecting, like WactDi for example. Also, I think a clear separation of local and global containers is needed.

  21. #21
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Separating registry from factory, is a good move. The factory is irrelevant for the user -- Only the registry should be part of the public interface. I'd like to see createInstance() become public. That way, the component can also be used for creating transient instances (Which might have shared dependencies).

    It's perhaps nitpicking, but I would prefer the name Container over Registry. Registry implies that you use a certain type of implementation (the Registry pattern), while Container might refer to different backends.

  22. #22
    SitePoint Addict
    Join Date
    Sep 2006
    Posts
    232
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yes, I find Container to be much more intuitive. But, I would include Container inside Di. So you end up having WactDi, a name that can automatically be associated with the pattern.

    PHP Code:
    class WactDi {
    }
    class 
    WactDiFactory {
    }
    class 
    WactDiContainer {


    class 
    WactDiStorage {

    WactDi can have 2 static methods, WactDi::createInstance() and WactDi::replaceObject() in case you want to use this component in a testing environment.

    Or, a user can create an instance of WactDiContainer, that stores the objects created by WactDiFactory inside WactDiStorage (that implements Iterator and WactDiStorageInterface, so users can register their own storage containers).

    --
    EDIT:

    The idea of having a static method to create an instance and replace objects comes from a TDD expert and creator of PHPSpec, Pádraic Brady. You can read his article here.
    Last edited by phpimpact; Feb 4, 2008 at 16:41.

  23. #23
    ********* Victim lastcraft's Avatar
    Join Date
    Apr 2003
    Location
    London
    Posts
    2,423
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Hi...

    The more I think about this, the more I think I got it right with Phemto (regarding Singletons that is, nothing else). The lifecycle should be set at registration time. The client of the class just shouldn't care. After all, there are other lifecycles such as "session" and "persistent" and "persistent in memory". If anything, these are more important than Singleton.

    The use case I'm thinking of is a database connection. It could have the following lifecycle rules:

    1) An instantiation will only be a connect() if there aren't currently enough connections. The real act of instantiation is to take ownership of the connection. I'm thinking of the Mysql bug that lets database sessions crosstalk over transactions.

    2) Destruction is actually just relinquishing ownership, ensuring unfinished transactions are rolled back, and returning the connection to a pool.

    My code should not need to know this...
    PHP Code:
    class InvoiceAction {
        function 
    __construct(DatabaseConnection $connection) { ... }
        ...

    I guess you would say that a ConnectionPool factory should do all of this, and that ConnectionPool is the Singleton. This is a legitimate answer, and would push a lot of features out of scope.

    In response to that I would say that if I'm writing an application in a framework, I'd write it initially without connection pooling. I'd then want to plug it in later, ideally without changing more than a configuration option.

    I might also want to use a third party pooling solution (not necessarily a database one...pooling could be a crosscut), say when I switch from/to APC. I probably have a customised Connection as well, and that will be the one referenced in my code to date.

    BTW: What use cases/stories are driving the current development? I take it acceptance tests are synonymous with unit tests at this early stage.

    yours, Marcus
    Marcus Baker
    Testing: SimpleTest, Cgreen, Fakemail
    Other: Phemto dependency injector
    Books: PHP in Action, 97 things

  24. #24
    SitePoint Addict
    Join Date
    Sep 2006
    Posts
    232
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    IMO, the most inspiring PHP DI container so far was the one created by Chris Corbyn, but unfortunately was left unfinished.

    And if you are a PicoContainer fan, the best port to PHP was made by DoubleCompile, he ported the latest version.

    And the one you are creating now Jeff is kind of different and I like where it's heading.

  25. #25
    Resident Code Monkey Chris Corbyn's Avatar
    Join Date
    Nov 2005
    Location
    Melbourne, Australia
    Posts
    713
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by phpimpact View Post
    IMO, the most inspiring PHP DI container so far was the one created by Chris Corbyn, but unfortunately was left unfinished.
    I still have it and actually registered a project on SF.net for it. The new version of Swift Mailer was is 100% DI-driven but I'm still not sure if I'll use that container yet, or just write simple factories. It's a beautiful way to code, especially when you take a test-first approach to development. If you nosey around the codebase in /branches/v4-simplified for swiftmailer you'll see how lloosely coupled it is compared with earlier versions, and also how good the test coverage is as a result.

    http://www.sourceforge.net/projects/phpcrafty (heck, I was so close to getting it public, there's even documentation and a partly built website :P I'll finish it I promise. Needs some optimizations.)

    (Don't pay too much attention to the new code in here... it's changing every few hours)
    http://swiftmailer.svn.sourceforge.n...v4-simplified/

    One thing with Crafty is that it's currently just a proof-of-concept and hasn't had any real-world application. It will likely need some tweaking.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •