SitePoint Sponsor

User Tag List

Results 1 to 17 of 17
  1. #1
    SitePoint Evangelist captainccs's Avatar
    Join Date
    Mar 2004
    Location
    Caracas, Venezuela
    Posts
    516
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)

    Dependency Injection Container

    A big thanks to all who took part in my last OOP thread. I've replaced Inheritance with Dependency Injection which does seem to make OOP life simpler. I wrote a class which was more procedural that OOP and then converted it into real OOP by offloading data (properties) to a config file. This required me to build a Config class to configure the specific objects created by the class. This turned out to be rather neat because it imports only those properties whose names match the variable names in the config file.

    Suppose in a project you have a "manager" object who uses a bunch of other objects via dependency injection. All the objects can get their configuration data, i.e. properties, from the same config_manager.php file. the Config class will make sure they get only what they need. All the manager object has to do is to supply the path to the config_manager.php file.

    Maybe I'm missing something but what does a "Dependency Injection Container" do that my Config class can't handle?

    PHP Code:
    <?php
    class Config {
        
        private 
    $_pathToConfig;
        private 
    $_vars;
        
        public function 
    __construct($_pathToConfig$_vars) {
            
    $this->_pathToConfig $_pathToConfig;
            
    $this->_vars $_vars;
        }

        public function 
    getConfig() {
            require 
    $this->_pathToConfig;
            foreach(
    $this->_vars AS $_key=>$_value) {
                
    $_var $_key;
                if(isset(${
    $_var})) {
                    
    $_result[$_key] = ${$_var};
                }
            }
            return 
    $_result;
        }

    // class Config
    ?>
    The calling class:
    PHP Code:
    <?
    class WildcardSubdomain {

        
    // config settings
        
    private $domain;
        private 
    $subdomains FALSE;
        private 
    $pathToError404;

        
    // class variables
        
    private $rewriteFlag FALSE;
        private 
    $host;
        private 
    $request;

        private 
    $subdomain FALSE;
        private 
    $pathToPage;

        public function 
    __construct($include) {
        
            
    $pathToConfig "{$include}config/".strtolower(get_class()).".php";
            
    $config = new Config($pathToConfigget_object_vars($this));
            foreach(
    $config->getConfig() AS $key=>$value) {
                
    $this->$key $value;
            }

        }

        
    // more class methods

    // class WildcardSubdomain
    ?>
    I believe this has all the functionality of a Dependency Injection Container. Am I missing something?
    Denny Schlesinger
    web services

  2. #2
    Community Advisor bronze trophy
    fretburner's Avatar
    Join Date
    Apr 2013
    Location
    Brazil
    Posts
    1,447
    Mentioned
    45 Post(s)
    Tagged
    13 Thread(s)
    Hi captainccs,

    I don't think your code is properly utilising dependency injection, at least, not as I understand it. I'm still studying this stuff myself, so I'd be interested to hear others' take on this.

    As I see it, your WildcardSubdomain class is dependant upon your Config class.. and as you're instantiating a new Config object within the constructor, you're creating a hard dependency on that class - this makes your code harder to test, and less flexible.

    To inject the dependency, you want to be passing it into the class, either through the constructor (when the dependency is required for the object to function), or via a setter method (for optional dependencies). In this case, you'd do something like this:
    PHP Code:
    public function __construct(Config $config) {
        
    // ...

    Now you can pass in any object which extends the Config base class/interface.. you could have config classes that load configuration from a different source (DB, JSON files etc) for example.

    What this now means of course, is that any time you want to create a new instance of WildcardSubdomain, you have to create (or retrieve) an instance of the Config class to pass in.. this is where dependency injection containers come in. You can tell the container about WildcardSubdomain, and the dependencies it requires (such as a Config object) and it can take care of wiring it all up whenever you need one.

  3. #3
    SitePoint Wizard bronze trophy Jeff Mott's Avatar
    Join Date
    Jul 2009
    Posts
    1,314
    Mentioned
    19 Post(s)
    Tagged
    1 Thread(s)
    I agree with everything fretburner just said.

    You may also find this article useful.

    http://fabien.potencier.org/article/...tion-container
    "First make it work. Then make it better."

  4. #4
    SitePoint Evangelist captainccs's Avatar
    Join Date
    Mar 2004
    Location
    Caracas, Venezuela
    Posts
    516
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by fretburner View Post
    Hi captainccs,

    I don't think your code is properly utilising dependency injection, at least, not as I understand it. I'm still studying this stuff myself, so I'd be interested to hear others' take on this.

    As I see it, your WildcardSubdomain class is dependant upon your Config class.. and as you're instantiating a new Config object within the constructor, you're creating a hard dependency on that class - this makes your code harder to test, and less flexible.
    Yes, I'm bending the rules a bit. First of all, WildcardSubdomain is a stand-alone class, a one off object that manages the URLs of a domain set up with wildcard subdomains. Once it does its stuff it hands of to the "real script" to which it is not connected in any way. BTW, it was a difficult class to test!

    Sooner or later I might have a different version of the Config class (ConfigBD?) and I'll probably do it differently in the other classes but there is a major conceptual difference between my Config class
    and the standard dependency injection container (DIC). DIC was created to relieve the coder from having to remember the details of the classes the main object has to call but it did not change the way the main class passes on the data. My Config class works on a different principle, it lets all the required objects find their own properties in the common config file. This way the objects are even more loosely coupled.

    I'm thinking that all I need to pass on is the config object created at the top of the heap (it needs a small tweak for that to work).
    Denny Schlesinger
    web services

  5. #5
    SitePoint Evangelist captainccs's Avatar
    Join Date
    Mar 2004
    Location
    Caracas, Venezuela
    Posts
    516
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Jeff Mott View Post
    I agree with everything fretburner just said.

    You may also find this article useful.

    http://fabien.potencier.org/article/...tion-container
    Jeff, thanks for the link. I'm collecting a lot of OOP reading material. I gather Fabien Potencier is one of the bright lights in OOP.
    Denny Schlesinger
    web services

  6. #6
    Community Advisor bronze trophy
    fretburner's Avatar
    Join Date
    Apr 2013
    Location
    Brazil
    Posts
    1,447
    Mentioned
    45 Post(s)
    Tagged
    13 Thread(s)
    Quote Originally Posted by captainccs View Post
    there is a major conceptual difference between my Config class and the standard dependency injection container (DIC)
    You seem to be referring to the two like they do similar things but in a different way but, as I see it, your Config class serves a completely different purpose to that of a DIC, and both could be used in building an application.

    Quote Originally Posted by captainccs View Post
    My Config class works on a different principle, it lets all the required objects find their own properties in the common config file. This way the objects are even more loosely coupled.

    I'm thinking that all I need to pass on is the config object created at the top of the heap (it needs a small tweak for that to work).
    Going back to your code example that we were discussing, it would be even better to avoid passing a config object at all. Also, the constructor of your WildcardSubdomain class is doing a lot of work that should be the responsibility of the Config class. What do you think about doing something like this:
    PHP Code:
    class Config {   
        protected 
    $_path
         
        public function 
    __construct($config_path) { 
            
    $this->_path $config_path
        } 
        
        public function 
    get($config_name) {
            
    $file $this->_path DIRECTORY_SEPARATOR $config_name '.php';
            if (
    file_exists($file)) {
                return 
    $this->_load($file);
            } else {
                throw new 
    Exception("Config file $file not found");
            }
        }
        
        protected function 
    _load($config_file) {
            require 
    $config_file;
            
    $config get_defined_vars();
            unset(
    $config['config_file']);
            return 
    $config;
        }

    Then your WildcardSubdomain class is simplified:
    PHP Code:
    class WildcardSubdomain 
        
    // config settings 
        
    protected $domain
        protected 
    $subdomains FALSE
        protected 
    $pathToError404

        
    // class variables..

        
    public function __construct($domain$subdomains$pathToError404) { 
            
    $this->domain $domain;
            
    $this->subdomains $subdomains;
            
    $this->pathToError404 $pathToError404;
        } 

        
    // more class methods..

    Creating a new instance:
    PHP Code:
    $config_reader = new Config('config/production');
    $config $config_reader->get('wildcardsubdomain');
    $subdomain = new WildcardSubdomain($config['domain'], $config['subdomains'], $config['404path']); 
    Now the class doesn't have any dependencies, and all the logic for handling config files is encapsulated in the Config class. If you want to change the mechanism for retrieving configuration data, you only have to do it in the one place.

  7. #7
    SitePoint Evangelist captainccs's Avatar
    Join Date
    Mar 2004
    Location
    Caracas, Venezuela
    Posts
    516
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    First of all, thanks for your thorough answer, it has given me a lot of food for thought.

    Quote Originally Posted by fretburner View Post
    You seem to be referring to the two like they do similar things but in a different way but, as I see it, your Config class serves a completely different purpose to that of a DIC, and both could be used in building an application.
    i believe the Config class does away with the need for DIC. It seems to me that DIC is needed only because of the way OOP evolved. First there was inheritance which proved messy. To clean up the mess they devised dependency injection which proved messy. To clean up the mess they devised DIC. What DIC does is to remove the need for the programmer to remember stuff about classes that will be injected way down the line. The config files do the exact same thing, when a class is instantiated the Config class passes directly to the object the same data (variables) that the DIC would have in a more complicated way.


    Going back to your code example that we were discussing, it would be even better to avoid passing a config object at all. Also, the constructor of your WildcardSubdomain class is doing a lot of work that should be the responsibility of the Config class.
    All the Config class does is to populate certain configuration properties. It has no other function.

    The website I'm doing is a condo management site which has three parts to it.

    The main domain is just html, css, javascript and maybe a couple of php includes (header and footer). These are the manager's display pages.

    There will be one "manager" sub-domain where the back-end management is done. Essentially one manager object with a lot of methods and some helper objects (database, etc.).

    The fun part is the one sub-domain per condominium (an account or a building). The manager can add or remove sub-domains as he adds or loses business. Each condominium object has a bunch of child objects (I'm not sure if they are called that). Each condo has a bunch of units. Each unit has people (owners or tenants) and accounts. Each condo also has the general expense account. There is probably more but I haven't gotten that far.

    My idea with the config files is this:

    Each condo has a file (or DB) with data common to all units. Objects related to the condo don't need all the data. For example, the database object just needs four items: (user, password, DBname and host). By matching the properties defined in the class to the variables in the config file, the Config object is able to populate just those properties. It happens at run time, no need for the programmer to worry about it. A single config file can service multiple classes. A class can access several config files. Let's suppose an owner has a config file (time will tell) he gets certain items from that file and others from the general condo config file.


    The WildcardSubdomain class is a special case. I was not able to manage the sub-domains with htaccess so I had to create the wildcard (*) sub-domain and the code to run it. The WildcardSubdomain class is prepended in htaccess. It checks the URL and redirects or rewrites it according to the established subdomains (data from the config file). The website manager (an object) adds or deletes subdomains as needed by the business. Once the WildcardSubdomain object is done, control is handed over to either a condo, the manager or to the main website html.
    PHP Code:
    $url = new WildcardSubdomain();
    include 
    $_SERVER['DOCUMENT_ROOT'].$url->getPathToPage();
    exit; 
    After this point the WildcardSubdomain class is of no further use (it's an htaccess replacement).

    Whether or not the WildcardSubdomain class has a dependency is of little importance. The same cannot be said of the Condo class which is a lot more complex. My reasoning is as follows, if the Condo class creates a Config object that points to its config file then all the child or dependent classes can use that Config object to extract the variables they need (and no others) from the common condo config file. If they need variables from another config file the can create their own Config object and also pass it on to its child or dependent classes. It all happens at run time.

    Isn't this what the DIC is supposed to achieve?

    BTW, this is the loop that filters the config variables
    PHP Code:
            foreach($this->_vars AS $_key=>$_value) { 
                
    $_var $_key
                if(isset(${
    $_var})) { 
                    
    $_result[$_key] = ${$_var}; 
                } 
            } 
            return 
    $_result
    I still need to work out a naming convention to prevent collisions.
    Denny Schlesinger
    web services

  8. #8
    SitePoint Evangelist captainccs's Avatar
    Join Date
    Mar 2004
    Location
    Caracas, Venezuela
    Posts
    516
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    On the other hand, there is one reason why I shouldn't create a Config object and call it from the various other objects that need it. I'd have to "guess" that some object will need it. My way it is created only if and when needed.
    Denny Schlesinger
    web services

  9. #9
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,194
    Mentioned
    17 Post(s)
    Tagged
    4 Thread(s)
    What your trying to do is essentially a much less powerful and flexible version of the config component within Symfony 2. I know I keep on harping on about Symfony 2 but many of these patterns are best explained through looking at components within that framework.
    The only code I hate more than my own is everyone else's.

  10. #10
    Community Advisor bronze trophy
    fretburner's Avatar
    Join Date
    Apr 2013
    Location
    Brazil
    Posts
    1,447
    Mentioned
    45 Post(s)
    Tagged
    13 Thread(s)
    Quote Originally Posted by captainccs View Post
    First of all, thanks for your thorough answer, it has given me a lot of food for thought.
    No worries, it's nice to get into a conversation about architectural concepts rather that just the nuts and bolts of PHP

    Quote Originally Posted by captainccs View Post
    i believe the Config class does away with the need for DIC. ... The config files do the exact same thing, when a class is instantiated the Config class passes directly to the object the same data (variables) that the DIC would have in a more complicated way.
    The purpose of a config class such as yours is to load configuration data for database connections (as you mention), mail servers, etc.. a DIC is a kind of object factory that knows how to make the objects that your app needs. Your config class doesn't do this, it doesn't create objects. When we're talking about dependencies, we're talking about other objects that an object needs to do its work.. not configuration data like passwords and DB server IPs.

    Some argue that DICs are overkill for relatively simple apps.. I'd probably start off without one and just introduce one if things started getting complicated.

    Quote Originally Posted by captainccs View Post
    All the Config class does is to populate certain configuration properties. It has no other function.
    That's fair enough, and a well designed class should only have one responsibility. But, my point was this - take a look at your original WildcardSubdomain constructor:
    PHP Code:
    public function __construct($include) { 
         
            
    $pathToConfig "{$include}config/".strtolower(get_class()).".php"
            
    $config = new Config($pathToConfigget_object_vars($this)); 
            foreach(
    $config->getConfig() AS $key=>$value) { 
                
    $this->$key $value
            } 

        } 
    This class knows about the Config object and how to create one, it also knows about where the config files are on the file system.. this is all stuff that only the Config class needs to know about. In this scenario, you could end up with many classes in your system which do the same thing in their constructors - if you change how this works down the line (e.g you change the directory layout or something) you then have to change every one of these classes.

    Quote Originally Posted by captainccs View Post
    Each condo has a file (or DB) with data common to all units. Objects related to the condo don't need all the data. For example, the database object just needs four items: (user, password, DBname and host). By matching the properties defined in the class to the variables in the config file, the Config object is able to populate just those properties. It happens at run time, no need for the programmer to worry about it. A single config file can service multiple classes. A class can access several config files. Let's suppose an owner has a config file (time will tell) he gets certain items from that file and others from the general condo config file.
    What kind of data are you talking about here? I'm getting the impression that you're talking of using your Config class to populate your app entities (condos, units, tenants etc.), is that right? If so, I think we could have been talking at cross purposes a little..

    Quote Originally Posted by captainccs View Post
    On the other hand, there is one reason why I shouldn't create a Config object and call it from the various other objects that need it. I'd have to "guess" that some object will need it. My way it is created only if and when needed.
    I don't see what you mean here.. whether you create a config object inside the contructor, or create it outside and pass it in, you're still only creating it for objects that need it. In fact, if you create a config object inside the constructor of any object that needs it, you will be creating many duplicate instances.. by passing it in, you can reuse a single instance.

  11. #11
    Community Advisor bronze trophy
    fretburner's Avatar
    Join Date
    Apr 2013
    Location
    Brazil
    Posts
    1,447
    Mentioned
    45 Post(s)
    Tagged
    13 Thread(s)
    Quote Originally Posted by oddz View Post
    What your trying to do is essentially a much less powerful and flexible version of the config component within Symfony 2. I know I keep on harping on about Symfony 2 but many of these patterns are best explained through looking at components within that framework.
    I think captainccs is building everything from scratch as a learning exercise, rather than because he has an app he needs to build.. but I'd agree with what you're saying, it's a really good idea to take a look at some frameworks and see how they've approached these problems. I've not used Symfony 2 myself, but I've heard that it's a good example of how to apply OOP techniques like DI.

  12. #12
    SitePoint Evangelist captainccs's Avatar
    Join Date
    Mar 2004
    Location
    Caracas, Venezuela
    Posts
    516
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by fretburner View Post
    The purpose of a config class such as yours is to load configuration data for database connections (as you mention), mail servers, etc.. a DIC is a kind of object factory that knows how to make the objects that your app needs. Your config class doesn't do this, it doesn't create objects. When we're talking about dependencies, we're talking about other objects that an object needs to do its work.. not configuration data like passwords and DB server IPs.
    This is the core issue but the Config class does the same thing as a DIC, it provides stuff needed down the line. At the end of the day stuff is stuff be it strings, arrays or objects and the way to get them is with a name/value pair:
    Code:
    Meta tag:      name="author"
                   content="Joe Writer"
    Attribute:     class="wide"
    Variable:      $this = "that" 
    Flag:          $reWrite = boolean (yes/no) (true/false) 
    Array:         $allThese = array('Joe', 'Moe', 'Curly') (list)
    Complex array: @appointments('Monday'=>'dentist',
                                 'Tuesday'=>'free',
                                 'Wednesday'=>'poker',
                                 'Sunday'=>'church', )
    Function:      doThis(withThat);
    Class:         Config { class definition }
    Object:        $button = new Fastener("clothes")
    It really is all the same! A DIC stores name/value pairs in a complicated way. A config file does it in a simple way but they are at heart the same thing! Just lists of name/value pairs. Sometimes the value is a list of values (arrays) or the value can be something as complex as an object but you still get at it by it name.
    Denny Schlesinger
    web services

  13. #13
    SitePoint Evangelist captainccs's Avatar
    Join Date
    Mar 2004
    Location
    Caracas, Venezuela
    Posts
    516
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by fretburner View Post
    I think captainccs is building everything from scratch as a learning exercise, rather than because he has an app he needs to build..
    Yes, a learning exercise but I also want to build a web-app (SaaS) that I think has a market here in Caracas. If I were a "professional" programmer I would most likely pick a framework instead of doing things from scratch but I'm retired and writing code is a lot of fun!

    This is where I'm at now: http://condo.condominiospcc.com/

    ... it's a really good idea to take a look at some frameworks and see how they've approached these problems.
    Yes! Learn from the masters.

    I haven't looked at the Symfony code but I did read a bit about it. They have a full featured Config class. Does Symfony use dependency injection containers?
    Denny Schlesinger
    web services

  14. #14
    Community Advisor bronze trophy
    fretburner's Avatar
    Join Date
    Apr 2013
    Location
    Brazil
    Posts
    1,447
    Mentioned
    45 Post(s)
    Tagged
    13 Thread(s)
    Quote Originally Posted by captainccs View Post
    I haven't looked at the Symfony code but I did read a bit about it. They have a full featured Config class. Does Symfony use dependency injection containers?
    Yeah it does: http://symfony.com/doc/current/compo...roduction.html. The docs also have a good introduction to DICs in general and why/how you'd use one.

  15. #15
    SitePoint Wizard bronze trophy Jeff Mott's Avatar
    Join Date
    Jul 2009
    Posts
    1,314
    Mentioned
    19 Post(s)
    Tagged
    1 Thread(s)
    Quote Originally Posted by captainccs View Post
    This is the core issue but the Config class does the same thing as a DIC, it provides stuff needed down the line. At the end of the day stuff is stuff be it strings, arrays or objects and the way to get them is with a name/value pair:
    Serializing the dependency information in a configuration format is certainly fine, but the main takeaway from fretburner's post(s) is that WildcardSubdomain should not know about the Config class. WildcardSubdomain should work just as well even if we didn't use the Config class at all. Otherwise what you're doing isn't even dependency injection, because there's no injection. Instead you have a dependency hardcoded into the WildcardSubdomain class.
    "First make it work. Then make it better."

  16. #16
    SitePoint Evangelist captainccs's Avatar
    Join Date
    Mar 2004
    Location
    Caracas, Venezuela
    Posts
    516
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Jeff Mott View Post
    Serializing the dependency information in a configuration format is certainly fine, but the main takeaway from fretburner's post(s) is that WildcardSubdomain should not know about the Config class. WildcardSubdomain should work just as well even if we didn't use the Config class at all. Otherwise what you're doing isn't even dependency injection, because there's no injection. Instead you have a dependency hardcoded into the WildcardSubdomain class.
    Jeff, I'm getting confused. WildcardSubdomain objects can manage any sub-domain configuration. The configuration cannot be hard coded into the class, instead the WildcardSubdomain object gets the configuration from the config.php file via the Config class.

    The framework has a certain architecture:
    Code:
    /includes/
    /includes/start.php
    /includes/classes/
    /includes/config/
    /includes/errorReporter/
    and a naming convention: A standalone object like WildcardSubdomain will use the class name in lower case. A condominium (a sub-domain) uses the condominium's id (avila). Every object can figure out the path to its config file. I don't see where I'm creating any dangerous dependency.

    As I talk to you guys I'm redesigning the structure of the framework. In the latest incarnation the caller supplies two parameters to the Config class: "path/to/file" and "array(vars to get)." Where is there a dependency issue? The caller knows about what it wants and where to get it. Config knows how to get it. Where is the problem?
    Denny Schlesinger
    web services

  17. #17
    Community Advisor bronze trophy
    fretburner's Avatar
    Join Date
    Apr 2013
    Location
    Brazil
    Posts
    1,447
    Mentioned
    45 Post(s)
    Tagged
    13 Thread(s)
    Quote Originally Posted by captainccs View Post
    The configuration cannot be hard coded into the class, instead the WildcardSubdomain object gets the configuration from the config.php file via the Config class.
    The problem is not about the configuration, the aim is to reduce the dependencies on other classes.

    Quote Originally Posted by captainccs View Post
    Every object can figure out the path to its config file. I don't see where I'm creating any dangerous dependency.
    I already pointed out where you have the dependency (in the contructor of WildcardSubdomain) and why its a potential problem.

    Quote Originally Posted by captainccs View Post
    Where is there a dependency issue? The caller knows about what it wants and where to get it. Config knows how to get it. Where is the problem?
    It's worth having a read through some of Misko Hevery's blog posts - specifically How to think about the "new" operator, and Flaw: Constructor does Real Work which go into some detail about this issue.


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
  •