SitePoint Sponsor

User Tag List

Results 1 to 10 of 10
  1. #1
    SitePoint Enthusiast
    Join Date
    Mar 2005
    Posts
    94
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    no-framework dependency injection framework

    I've just read some threads about DI and someone complained about complexity involved with DI. I just wanted to clarify that the concept of DI is simple, the implementations of DI frameworks tend to be complex.

    Following is a example how DI could work without any framework level code. With anonymous functions, it's actually extremely flexible and simple.

    Please note that the code is just written down to make some sense but it's not perfect, nor error free.

    PHP Code:
    $deps = new stdClass;
    $deps->db = new DB('user''pass''etc');
    $deps->auth = new Auth();
    $deps->user = new User($db$auth);
    $deps->session = new Session('ttl');
    $deps->session->setUser($user);
    $deps->router = new Router();

    $deps->behaviorFactory = function($behaviorClass$model) {
        return new 
    $behaviorClass($model);
    }
    $deps->modelFactory = function($modelClass) use(&$deps) {
        return new 
    $modelClass($deps->db$deps->behaviorFactory);
    }
    $deps->controllerFactory = function($controllerClass) use(&$deps) {
        return new 
    $controllerClass($deps->session$deps->modelFactory);
    }
    $deps->dispatcher = new Dispatcher($deps->controllerFactory$deps->router);
    // start the dispatcher somewhere 
    If someone is able to figure out some major flaws (in terms of functionality) in that approach, i'm happy to hear, i can't imagine any right now.

  2. #2
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    988
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    how is that DI? Presumably to get objects for $deps you need to pass $deps around? That's a registry rather than DI unless i'm missing something.

  3. #3
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    988
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    Nevermind, after a second look I can see what you're trying to do.

    Looks quite clever!

    How can you handle situations where certain classes require a new instance of an object? For example if your model class wanted a different version of behaviorFactory.

  4. #4
    SitePoint Addict
    Join Date
    Sep 2004
    Posts
    211
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Isn't this:

    PHP Code:
    $deps->modelFactory = function($modelClass) use(&$deps) { 
        return new 
    $modelClass($deps->db$deps->behaviorFactory); 

    $deps->controllerFactory = function($controllerClass) use(&$deps) { 
        return new 
    $controllerClass($deps->session$deps->modelFactory); 

    $deps->dispatcher = new Dispatcher($deps->controllerFactory$deps->router); 
    // start the dispatcher somewhere 


    suppose to be this:

    PHP Code:
    $deps->modelFactory = function($modelClass) use(&$deps) { 
        return new 
    $modelClass($deps->db$deps->behaviorFactory()); 

    $deps->controllerFactory = function($controllerClass) use(&$deps) { 
        return new 
    $controllerClass($deps->session$deps->modelFactory()); 

    $deps->dispatcher = new Dispatcher($deps->controllerFactory(), $deps->router); 
    // start the dispatcher somewhere 
    ?

    Meaning instead of passing the anonymous function, you actually pass the result of the function (by calling it)? Or am I missing something?

  5. #5
    SitePoint Enthusiast
    Join Date
    Mar 2005
    Posts
    94
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    @tomB, @acid24ro

    You both refer to same problem from different perspectives. Note that the factories do accept class names, so you can retrieve different objects with them. This could also be extended with some flow control, asking which class is requested and injecting additional dependencies. It's just a way to resolve dependencies at "runtime".

  6. #6
    SitePoint Addict
    Join Date
    Sep 2004
    Posts
    211
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by elias View Post
    @tomB, @acid24ro

    You both refer to same problem from different perspectives. Note that the factories do accept class names, so you can retrieve different objects with them. This could also be extended with some flow control, asking which class is requested and injecting additional dependencies. It's just a way to resolve dependencies at "runtime".
    Can you post a little code example of an implementation for a model class or controller class or Dispatcher class?

  7. #7
    SitePoint Addict webaddictz's Avatar
    Join Date
    Feb 2006
    Location
    Netherlands
    Posts
    295
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by elias View Post
    PHP Code:
    $deps = new stdClass;
    $deps->db = new DB('user''pass''etc');
    // [...] 
    If someone is able to figure out some major flaws (in terms of functionality) in that approach, i'm happy to hear, i can't imagine any right now.
    Not a major flaw, but I do see a small negative effect on using this code: you now always instantiate each and every possible dependency before starting to wire the objects together. It's a minor issue, but it's not that every request specifically uses the database, session or user, so this might become a bottleneck? Putting them in a (anonymous) function which returns a new instance when it's called would probably fix the issue.
    Yes, I blog, too.

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

    Quote Originally Posted by webaddictz View Post
    you now always instantiate each and every possible dependency before starting to wire the objects together.
    You could place the instantiation in a block. I kind of think it would be simpler if you always did this. You would then basically have implemented "needle" (an old Ruby DI tool) in PHP. Fabien Potencier does something similar in his PHP talks.

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

  9. #9
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Here is my latest incarnation... with optional Reflection usage, as opposed to always using it that the previous version I posted.

    PHP Code:
    class Container
    {
        protected 
    $factories;

        function 
    __construct(array $factories = array())
        {
            
    $this->factories $factories;
            
    $this->registerInstance($this__CLASS__);
        }

        function 
    register($interface$factory)
        {
            if (
    is_string($factory))
            {
                
    $this->register($interface,
                    function(
    Container $container) use ($factory$interface)
                    {
                        
    $factory $container->getDefaultFactory($factory);
                        
    $container->register($interface$factory);
                        return 
    $factory($container);
                    });
            }
            else
            {
                
    $this->factories[$interface] = $factory;
            }
        }

        function 
    registerInstance($instance$interface null)
        {
            
    $factory = function() use ($instance) { return $instance; };
            if (
    $interface)
            {
                
    $this->factories[$interface] = $factory;
            }
            else
            {
                
    $this->factories[get_class($instance)] = $factory;
                foreach(
    class_implements($instance) as $interface)
                    
    $this->factories[$interface] = $factory;
                foreach(
    class_parents($instance) as $interface)
                    
    $this->factories[$interface] = $factory;
            }
            return 
    $instance;
        }

        function 
    registerShared($interface$factory)
        {
            if (
    is_string($factory))
            {
                
    $this->register($interface,
                    function(
    Container $container) use ($factory$interface)
                    {
                        
    $factory $container->getDefaultFactory($factory);
                        return 
    $container->registerInstance($factory($container), $interface);
                    });
            }
            else
            {
                
    $this->register($interface,
                    function(
    Container $container) use ($factory$interface)
                    {
                        return 
    $container->registerInstance($factory($container), $interface);
                    });
            }
        }

        function 
    create($interface)
        {
            if (isset(
    $this->factories[$interface]))
                
    $factory $this->factories[$interface];
            else
                
    $factory $this->factories[$interface] = $this->getDefaultFactory($interface);

            return 
    $factory($this);
        }

        function 
    get($interface)
        {
            
    $container $this;
            return function() use (
    $container$interface)
            {
                return 
    $container->create($interface);
            };
        }

        protected function 
    getDefaultFactory($className)
        {
            
    $class = new ReflectionClass($className);
            try
            {
                
    $constructor $class->getConstructor();
            }
            catch (
    ReflectionException $exception)
            {
                
    $constructor null;
            }
            if (
    $constructor && $constructor->getNumberOfParameters())
            {
                return function(
    Container $container) use ($class$constructor)
                {
                    
    $args = array();
                    foreach(
    $constructor->getParameters() as $parameter)
                    {
                        
    $parameterClass $parameter->getClass();
                        if (
    $parameterClass)
                            
    $args[] = $container->create($parameterClass->getName());
                        else if (
    $parameter->isDefaultValueAvailable())
                            
    $args[] = $parameter->getDefaultValue();
                        else
                            throw new 
    DependencyCreationException($parameter);
                    }
                    return 
    $class->newInstanceArgs($args);
                };
            }
            else
            {
                return function() use (
    $className)
                {
                    return new 
    $className();
                };
            }
        }


  10. #10
    SitePoint Enthusiast
    Join Date
    Mar 2005
    Posts
    94
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Sorry, little late

    Quote Originally Posted by lastcraft View Post
    You could place the instantiation in a block. I kind of think it would be simpler if you always did this. You would then basically have implemented "needle" (an old Ruby DI tool) in PHP. Fabien Potencier does something similar in his PHP talks.

    yours, Marcus
    Agreed with the closures for lazy loading.

    @Ren

    How do you handle cases where you need to pass constructor arguments to constructors? Do you try to avoid it in your application?


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
  •