SitePoint Sponsor

User Tag List

Page 2 of 5 FirstFirst 12345 LastLast
Results 26 to 50 of 116
  1. #26
    SitePoint Evangelist ghurtado's Avatar
    Join Date
    Sep 2003
    Location
    Wixom, Michigan
    Posts
    591
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Ok, I wanna play. Lets see how this works out.

    The example below is a (semi)real case use of my own that I have run into on many occassions while desigining and testing the checkout process for ecommerce sites. First, the code:
    PHP Code:
    <?
    class Checkout {
        function 
    execute(){
            
    $store = new Store// one of many possible stores
            
    $order = new Order($_POST); // one of many sources for customer data
            
            
    $store->setOrder($order);
            
            if(
    $store->submitOrder()){
                return 
    true;
            } else {
                return 
    false;
            }
        }
    }

    class 
    Store {
        var 
    $order;
        
        function 
    setOrder($order){
            
    $this->order $order;
        }
        
        function 
    submitOrder(){
            
    $payment = new PaymentGateway($order); // one of many possible classes
            
            
    if($payment->checkPayment()){
                
    $order->save(); // save to DB
                
    return true;
            } else {
                return 
    false;
            }
        }
    }

    // Now lets checkout

    $checkout = new Checkout();

    if(
    $checkout->execute()){
        echo 
    "Thank you for your order!";
    } else {
        echo 
    "Your payment information is invalid";
    }
    ?>
    I have intentionally oversimplified the checkout process to reduce noise, and followed some bad coding practises to work as "targets" for DI refactoring.

    Now, the problems:
    • I would like to reuse my checkout class with different implementations of the "Store" class, since I may have different versions of the same store that go through roughly the same checkout process, but pull their catalog data from different sources.
    • I would like to be able to use different sources for customer data in my checkout process, currently $_POST is hardcoded and I cant accept data from other sources, like say $_SESSION, or an array, say for Unit Testing.
    • (This is probably the most important and most "Real" of all quagmires with the design) I would like to be able to use different payment classes, not just for testing, but several of them might even have to be swapped on the fly during the checkout process depending on the customer's choice of payment method. I would like this "switch" to happen at the class registration level of the DI, so that it can be configured easily.


    So there it is, a pretty crappy design that could benefit greatly from a smart Dependency Injector. I tried to keep it simple enough, yet realistic, with a couple of places where dependencies can be extracted.

    I suppose the first step would be to refactor the classes so that they would even allow themselves to be injected, moving instantiation to setters or constructor parameters. My idea is that once we do that as a group exercise, we can then easily extract the paradigm of how we need our injector to work, without having a single line of injector code written.

    I would like to know everyone's thoughts about this use case, feel free to chip in or hack away at it.
    Garcia

  2. #27
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I have plenty of legacy code like this (or ever worse ... global $conn; ).

    My first step in getting things under control is to allow for DI in order to be able to get MockObjects into the works for testing. My first cut at the Checkout class would look like:

    PHP Code:
    class Checkout {
        var 
    $_store;
        var 
    $_order;

        function 
    Checkout(&$store, &$order) {
            
    $this->_store =& $store;
            
    $this->_order =& $order;
        }

        function 
    execute(){
            
    $this->_store->setOrder($this->_order);
            
            if(
    $this->_store->submitOrder()){
                return 
    true;
            } else {
                return 
    false;
            }
        }

    And for the Store class:
    PHP Code:
    class Store {
        var 
    $_order;
        var 
    $_payment_gateway;
        
        function 
    Store(&$payment_gateway) {
            
    $this->_payment_gateway =& $payment_gateway;
        }

        function 
    setOrder(&$order){
            
    $this->_order =& $order;
            
    $this->_payment_gateway->setOrder($order);
        }
        
        function 
    submitOrder(){
            if(
    $this->_payment_gateway->checkPayment()){
                
    $this->_order->save(); // save to DB
                
    return true;
            } else {
                return 
    false;
            }
        }

    Now due to timing of when we wanted to create the payment gateway and when we wanted to process the order I needed to create a setter method for the order. Is this allowed in your design? If not, there are tricks to cope with that...

    And now that we have injectable classes, I will let someone else take a swag at the DI framework to wrap around the whole script.
    Jason Sweat ZCE - jsweat_php@yahoo.com
    Book: PHP Patterns
    Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
    Detestable (adjective): software that isn't testable.

  3. #28
    ********* 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 ghurtado
    Ok, I wanna play. Lets see how this works out.
    Game on .

    Quote Originally Posted by ghurtado
    The example below is a (semi)real case use of my own that I have run into on many occassions while desigining and testing the checkout process for ecommerce sites.
    OK, here it is Phemto'd roughly in it's current version. Your use case already points up some flaws in the interface (it's a plaything) so really I should learn Pico. Anyway...
    PHP Code:
    <?
    class Checkout {
        function 
    execute() {
            
    $injector Injector::instance();
            
    $store $injector->create('Store');
            
            if(
    $store->submitOrder()){
                return 
    true;
            } else {
                return 
    false;
            }
        }
    }

    class 
    MyStore implements Store {
        var 
    $order;
        
        function 
    __construct(Order $order){
            
    $this->order $order;
        }
        
        function 
    submitOrder(){
            
    $injector Injector::instance();
            
    $payment $injector->create(
                            
    'PaymentGateway',
                            
    $this->order);
                     
    // Supplying candidate instances doesn't work yet.
            
            
    if($payment->checkPayment()){
                
    $this->order->save(); // save to DB
                
    return true;
            } else {
                return 
    false;
            }
        }
    }

    interface 
    Order {
        function 
    __construct(Request $request);
    }

    class 
    PostRequest implements Request { ... }
    class 
    PaypalGateway implements PaymentGateway { ... }
    Now lets checkout...
    PHP Code:
    $injector Injector::instance();
    $injector->register('MyOrder');
    $injector->register('MyStore');
    $injector->registerSingleton('PostRequest');
    $injector->register('PaypalGateway');

    $checkout = new Checkout();

    if(
    $checkout->execute()){
        echo 
    "Thank you for your order!";
    } else {
        echo 
    "Your payment information is invalid";
    }
    ?> 
    Quote Originally Posted by ghurtado
    I would like to reuse my checkout class with different implementations of the "Store" class, since I may have different versions of the same store that go through roughly the same checkout process, but pull their catalog data from different sources.
    No problem .

    Quote Originally Posted by ghurtado
    I would like to be able to use different sources for customer data in my checkout process, currently $_POST is hardcoded and I cant accept data from other sources, like say $_SESSION, or an array, say for Unit Testing.
    No problem .

    Quote Originally Posted by ghurtado
    I would like to be able to use different payment classes, not just for testing, but several of them might even have to be swapped on the fly during the checkout process depending on the customer's choice of payment method. I would like this "switch" to happen at the class registration level of the DI, so that it can be configured easily.
    If each payement method results in an interface instantiation, fine. Say...
    PHP Code:
    class BankOfAmericaMoneyOrderTransfer implements MoneyOrderTransfer { ... } 
    The branch in the code just needs...
    PHP Code:
    case MONEY_TRANSFER:
        
    $payment $injector->create('MoneyOrderTransfer'); 
    No problem .

    Quote Originally Posted by ghurtado
    I would like to know everyone's thoughts about this use case, feel free to chip in or hack away at it.
    I think it's quite easy with trivial modifications to Phemto (thanks for the perfect input as I am gathering use cases now). Pico almost certainly does all of this already.

    yours, Marcus

    p.s. Thanks for pasting in those tabs. Grrrr...
    Marcus Baker
    Testing: SimpleTest, Cgreen, Fakemail
    Other: Phemto dependency injector
    Books: PHP in Action, 97 things

  4. #29
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Small changes:

    PHP Code:
    class Checkout {
        var 
    $_store;
        var 
    $_order;

        function 
    Checkout(&$store, &$order) {
            
    $this->_store =& $store;
            
    $this->_order =& $order;
        }

        function 
    execute(){
            return 
    $this->_store->submitOrder($this->_order);
        }
    }

    class 
    Store {
        var 
    $_order;
        var 
    $_payment_gateway;
        
        function 
    Store(&$payment_gateway) {
            
    $this->_payment_gateway =& $payment_gateway;
        }
        
        function 
    submitOrder(&$order){
            
    $this->_payment_gateway->setOrder($order);
            if(
    $this->_payment_gateway->checkPayment()){
                return 
    $order->save(); // save to DB
            
    }
        }

    IMO, from this stage, the above code shouldn't need to be changed in order to inject the dependencies.

    And I'd also start wondering about the Checkout class itself at this stage... a class for one line of code?

    Douglas
    Last edited by DougBTX; Sep 8, 2005 at 03:43.
    Hello World

  5. #30
    SitePoint Guru dagfinn's Avatar
    Join Date
    Jan 2004
    Location
    Oslo, Norway
    Posts
    894
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by lastcraft
    PHP Code:
    $injector Injector::instance();
    $store $injector->create('Store'); 
    Excuse me, I'm not an expert on this, but I just re-read Fowler's article on DI. Isn't this actually a Service Locator? And more specifically, a dynamic one? Or have I misunderstood something?

    According to Fowler's advice, a Service Locator is probably a better choice in the circumstances. So I'm not critcising the solution; I'm just trying to understand the concepts.
    Dagfinn Reiersøl
    PHP in Action / Blog / Twitter
    "Making the impossible possible, the possible easy,
    and the easy elegant"
    -- Moshe Feldenkrais

  6. #31
    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 dagfinn
    Isn't this actually a Service Locator?
    I suppose you could say that a DI is a specialized form of a ServiceLocator. The main difference is that the DI is used outside the subject class, where the ServiceLocator is used from within. The difference becomes more obvious when there are several classes depending on each other.

    The ServiceLocator :
    PHP Code:
    class Foo
    {
        var 
    $bar;
        function 
    Foo(&$components) {
            
    $this->bar =& $components->createInstance('Bar');
        }
    }

    ...

    $components =& new ServiceLocator();
    $foo =& $components->createInstance('Foo'); 
    The DI :
    PHP Code:
    class Foo
    {
        var 
    $bar;
        function 
    Foo(&$bar) {
            
    $this->bar =& $bar;
        }
    }

    ...

    $components =& new DependencyInjector();
    $foo =& $components->createInstance('Foo'); 
    On the outside the two does the same - but for the class Foo, the difference is that in the first example, Foo depends on Bar and on ServiceLocator, while in the second, Foo only depends on Bar.

  7. #32
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken
    The main difference is that the DI is used outside the subject class, where the ServiceLocator is used from within. [...]

    The ServiceLocator :
    PHP Code:
    class Checkout 
        function 
    execute() { 
            
    $components ServiceLocator::instance(); 
            
    $store $components->createInstance('Store'); 
             
            if(
    $store->submitOrder()){ 
                return 
    true
            } else { 
                return 
    false
            } 
        } 

    It seems to fit, sounds like dagfinn is right.

    Douglas
    Hello World

  8. #33
    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 DougBTX
    PHP Code:
    $components ServiceLocator::instance(); 
    $store $components-> createInstance('Store'); 
    That is definately a ServiceLocator. And a global one too (singleton). It could be even simpler if made static :
    PHP Code:
    $store Components::CreateInstanceOf('Store'); 
    Or more complex, but also more flexible, by passing the servicelocator in. (As in my previous post).

    That would all depend on the degree of flexiblity you want in your application.

  9. #34
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by lastcraft

    OK, here it is Phemto'd roughly in it's current version. Your use case already points up some flaws in the interface (it's a plaything) so really I should learn Pico.
    Hey, no fair using either Phempto or Pico becuase this thread is DI in PHP4, right?

  10. #35
    ********* 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 dagfinn
    Excuse me, I'm not an expert on this, but I just re-read Fowler's article on DI. Isn't this actually a Service Locator?
    Yeah, I guess so in this context. I'm still wrapping my head around this stuff too .

    I didn't go far enough with the example. Another way is to just move it to the constructor as Jason did. One difference though, is that further dependencies are wired up automatically, right down to the Request in this case. That dependency is completely hidden in the code except for the type hint.

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

  11. #36
    SitePoint Evangelist ghurtado's Avatar
    Join Date
    Sep 2003
    Location
    Wixom, Michigan
    Posts
    591
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I like the responses so far, I think we are down the right path.

    Quote Originally Posted by sweatje
    Now due to timing of when we wanted to create the payment gateway and when we wanted to process the order I needed to create a setter method for the order. Is this allowed in your design? If not, there are tricks to cope with that...
    Yes, of course. For the sake of completeness, let me introduce a mock Payment class.

    PHP Code:
    class Payment {
        var 
    $_order;
        
        function 
    setOrder(&$order){
            
    $this->_order =& $order;
        }

        function 
    checkPayment(){
            
    // do some calls to remote gateway
            // $curl = curl_init('https://www.example.com/payment_gateway');
            // curl_setopt($curl, CURLOPT_POSTFIELDS, $this->_order);
            // $status = curl_exec(); // needless to say, this doesnt work
            
            
    if($status == STATUS_SUCCESS){
                return 
    true;
            } else {
                return 
    false;
            }    
        }

    "Payment" can be extended so long as the child Payment class implements "checkPayment". I have commented out the lines that would actually process the cURL request since they are just there to "add fat".

    Marcus,

    I like the general style of solving the dependencies with Phemto at the top level, in that the wiring is rather minimal.
    PHP Code:
    $injector Injector::instance();
    $injector->register('MyOrder');
    $injector->register('MyStore');
    $injector->registerSingleton('PostRequest');
    $injector->register('PaypalGateway'); 
    This top level service locator aspect of it seems rather elegant, in that you are not required to know the dependencies, it seems that Phemto is smart enough to figure them out.

    However, does this work with setter injection currently as well as constructor injection? I suppose that would be ideal, although my guess is that you get around that problem by requiring registered services to instanciate / use the service locator whenever they need a class instance themselves. Which leads me to...

    Quote Originally Posted by DougBTX
    IMO, from this stage, the above code shouldn't need to be changed in order to inject the dependencies.
    This concept is key here. As others have pointed out, as soon as the "services" have to be aware of the injector, the injector starts to seem a little bit more like a service locator. I think it is that level of coupling that we would want to avoid, by using a "pure" dependency injector. After all, the greatest incentive anyone would have to use DI in their projects is that of not having to modify any of their classes. Sure, you need to follow some basic rules, like using setters or construction arguments for external dependencies, but these are good practises any developer should be following anyway.

    So lets see how we can design the top level wiring of all these classes without them being aware of being injected. I like to think of it as an unsuspecting child who is brought to the doctor for vaccinations, and when he is looking the other way, bam! by the time he's thinking about crying, he has already been "injected" and is ready to go home (ok, lame analogy, but you get my point)

    So I guess in our case, we would need something that allows us to do:
    PHP Code:
    // from the bottom up, just because
    $injector->registerService('Order''PostOrder'); // extends 'Order'
    $injector->registerService('Payment''PaypalPayment');
    $injector->registerService('Store''CameraStore');
    $injector->registerService('Checkout''TestCheckout');

    // this bottom part should never need to change if the 
    // interfaces for these classes remain stable
    $injector->registerSetter('Payment''setOrder''Order'); // setter injection
    $injector->registerSetter('Store''Store''Payment'); // constructor injection
    $injector->registerSetter('Checkout''Checkout', array('Store''Order')); // constructor injection 
    I kinda like this syntax, in that it is very close to a config file "look", which is ultimately where you might want to elevate these types of wiring decisions in a full blown framework. But at the same time it allows you to cleanly see your class dependencies and change the selected implementations of each class in one single centralized place.

    While it may seem a little verbose, the PHP5 version of the injector would probably be quite a bit more concise by using reflection.

    What do you guys think?

    PS.
    Quote Originally Posted by DougBTX
    And I'd also start wondering about the Checkout class itself at this stage... a class for one line of code?
    Well, in the full blown real-world case, this is not so, but I wanted to keep things clean to be able to see the DI at use more clearly. The main reason I introduced it here, is for an additional level in the dependency tree, but you can assume that the class has a little more meat in the execute method. Like this if you want:

    PHP Code:
        function execute(){
            if(
    $this->_store->submitOrder($this->_order)){
                
    Logger::log('Successful order'$this->_order);
                
    mail(STORE_OWNER'New Order''A new order has been placed');
                
                return 
    true;
            } else {
                return 
    false;
            }
        } 
    Quote Originally Posted by lastcraft
    p.s. Thanks for pasting in those tabs. Grrrr...
    "Thanks" or "Grrrr..." ?
    Garcia

  12. #37
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by ghurtado
    So I guess in our case, we would need something that allows us to do:
    Here's a testcase for the following methods:

    DI::clear(); Clears internal registries, used in setUp() - a symptom of using a singleton-style DI class rather than a real object

    DI::add(string $name, string $class [, array of mixed $signature]) A combination of register, registerSingleton and registerService. $name is the identifier we use to get instances of objects out. $class is the class name of an object. $signature is a list of names of objects needed for the object's constructor. Gaps in the array (where a name is replaced by null) must be supplied when an instance of the class is requested. Dependencies cannot be made on $names with incomplete $signatures.

    DI::set(string $name, object &$object) This is a bit more like a normal Registry. $name is the identifier we use to get instances of objects out. $object is an instance of an object we want to associate with the $name

    DI::get(string $name [, array of mixed $paramaters]) Returns an instance which corresponds to $name. If an object was supplied to DI::add, then a reference to that object is returned. If a class name was supplied, a new instance of that class is returned. Any dependencies are resolved using the names provided in the $signature provided in DI::add. Any gaps in the signature are replaced with the elements in $paramaters, were the order and count of the elements directly match the nulls in $signature.

    PHP Code:
    class TestOfDI extends UnitTestCase
    {
        
        function 
    setUp ( )
        {
            
    DI::clear();
        }
        
        function 
    test_creation ( )
        {
            
    $test_payment_gateway =& new MyPaymentGateway('paypal');
            
            
    DI::add('Checkout''MyCheckout', array('Store''Order'));
            
    DI::add('Store',    'MyStore',    array('Gateway'));
            
    DI::add('Order',    'MyOrder');
            
    DI::set('Gateway',  $test_payment_gateway);
            
            
    $a =& DI::get('Checkout'); // a new instance
            
    $b =& DI::get('Gateway');  // the original instance
            
    $c =& DI::get('Store');    // a new instance
            
            
    $this->assertIsA($a'MyCheckout');
            
    $this->assertIsA($b'MyPaymentGateway');
            
    $this->assertIsA($c'MyStore');
        }
        
        function 
    test_empty_slots ( )
        {
            
    $test_payment_gateway =& new MyPaymentGateway('paypal');
            
    $test_order =& new MyOrder;
            
            
    DI::add('Checkout''MyCheckout', array('Store'null));
            
    DI::add('Store',    'MyStore',    array('Gateway'));
            
    DI::set('Gateway',  $test_payment_gateway);
            
            
    $a =& DI::get('Checkout'); // error, not all arguments supplied
            
    $b =& DI::get('Checkout', array($test_order));  // a new instance
            
            
    $this->assertError();
            
    $this->assertEqual($a, (object) null);
            
    $this->assertIsA($b'MyCheckout');
        }
        

    It uses these classes, copied from posts above:

    PHP Code:
    class MyOrder {
        var 
    $_items;
        
        function 
    setItems($items){
            
    $this->_items $items;
        }
        
        function 
    getItems($items){
            return 
    $this->_items;
        }
        
    }

    class 
    MyCheckout 
        var 
    $_store;
        var 
    $_order;

        function 
    MyCheckout(&$store, &$order){ 
            
    $this->_store =& $store
            
    $this->_order =& $order
        } 

        function 
    execute(){ 
            return 
    $this->_store->submitOrder($this->_order); 
        } 


    class 
    MyStore 
        var 
    $_order
        var 
    $_payment_gateway
         
        function 
    MyStore(&$payment_gateway){ 
            
    $this->_payment_gateway =& $payment_gateway
        } 
         
        function 
    submitOrder(&$order){ 
            
    $this->_payment_gateway->setOrder($order); 
            if(
    $this->_payment_gateway->checkPayment()){ 
                return 
    $order->save(); // save to DB 
            

        } 
    }

    class 
    MyPaymentGateway 
        var 
    $_order
         
        function 
    setOrder(&$order){ 
            
    $this->_order =& $order;
        } 

        function 
    checkPayment(){ 
            
    // do some calls to remote gateway 
            // $curl = curl_init('https://www.example.com/payment_gateway'); 
            // curl_setopt($curl, CURLOPT_POSTFIELDS, $this->_order); 
            // $status = curl_exec(); // needless to say, this doesnt work 
            
            
    return $status == STATUS_SUCCESS;
        } 

    Douglas
    Last edited by DougBTX; Sep 8, 2005 at 12:56. Reason: updates
    Hello World

  13. #38
    SitePoint Evangelist ghurtado's Avatar
    Join Date
    Sep 2003
    Location
    Wixom, Michigan
    Posts
    591
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thank you for the Unit Tests, Douglas. I think we are on the right track with defining the tests up front.

    I have to say, though, that I have some questions about your choices for the DI interface. It seems to me that the DI::add method is a bit loaded in how it works, maybe it tries to do too many things at once? The main problem I see with the method is that it only seems to support constructor injection, but I think it would be useful to support setter injection as well. That may have been part of the reason behind me splitting it in two in my previous post ("registerService" and "registerSetter").

    On the other hand, DI::set does a very similar thing and I think it could be combined with add. Since the second parameter would be either a string or an object, we could decide within the method implementation whether we are asking it to register a class with the named service, for later instantiation (if you pass a string, what "add" currently does), or directly drop an externally instantiated object into the registry (if you pass an object, much like "set" currently works).

    Might it be best to call it "addService/getService" instead?

    Perhaps since we are dealing with PHP4 and its lack of reflection, the best thing would be to pass to the DI methods an object that defines our signatures, whether they be constructor based or setter based. That way we leave the door open for a PHP5 version where this "classSignature" object is automatically created via reflection.

    I think get works pretty much like it should, you ask for a service, and you get and object back. Not much to dwell on here.

    It would be interesting to hear how other members of the forum would like the interface to work, and what requirements it would need to have in order to be useful to them.

    I'm just thinking out loud, so feel free to point out the problems with these ideas.
    Garcia

  14. #39
    SitePoint Member
    Join Date
    Jun 2004
    Location
    London
    Posts
    6
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by lastcraft
    Why not add the extra information at the registration stage? One of the nice things about DI is the minimal impact it has on the components.
    yes, good point. Originally I had thought of doing it at registration, but felt uncomfortable about part of the description of classes being in a separated from the class definition itself. Also it would mean more leg-work for the framework client. Although on the other hand the point of DI is to give control over the wiring to the client.

    As you suggest, if you want to combine components from different frameworks, then really you want to avoid requiring those components to be modified. Stating the interfaces and dependencies at registration time avoids this.

    Quote Originally Posted by lastcraft
    Having to implant a reflection API could be a big deal, especially with PHP5 "around the corner".
    well, a full reflection API is not needed, just a convention for these two methods. It's really not a big amount of work. I think in PHP4 it would be nice to have both options, so you could have a default if it's supported by the components and the possibility to override at registration time if needed.

    Dave

  15. #40
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by ghurtado
    On the other hand, DI::set does a very similar thing and I think it could be combined with add.
    Yes, so do I. Any idea how to pass a string by reference without getting a fatal error?

    Quote Originally Posted by ghurtado
    The main problem I see with the method is that it only seems to support constructor injection, but I think it would be useful to support setter injection as well.
    That could be put in, I left it out because it isn't something I need. I thought I'd leave it to someone else who does want it, as they'd have a better idea of what they want than I do

    I'd like to keep the name of the class and the constructor fields together, mainly so I can keep them on the same line and there is no question about which constructor fields apply to which class.

    It might be possible to make the array in DI::add associative, with the keys the setter method names, or add another method to set setters which can apply to objects comming from DI::set too.

    With DI::set/get, it feels more like a normal registry with value added DI. Here's the blurb I wrote for my doc comments as I was writing an implementation for the testcase:

    Code:
    DI, for Dependency Injection. It is basically a registry, using
    DI::set and DI::get, with the added ability to create instances
    of classes using constructor injection. You can add constructor
    signatures using DI::add, then get new instances using DI::get.
    Quote Originally Posted by ghurtado
    That may have been part of the reason behind me splitting it in two in my previous post ("registerService" and "registerSetter").
    I noticed you used registerSetter for both setter and constructor injection, is that a good thing?

    Quote Originally Posted by ghurtado
    Might it be best to call it "addService/getService" instead?
    Service seems too general but to specific to me, I'm not sure what it means here, so I'm hesitant to use it.

    Quote Originally Posted by ghurtado
    That way we leave the door open for a PHP5 version where this "classSignature" object is automatically created via reflection.
    You'll have to sell me on using type hinting / interfaces for this. I'm not sure how that would fit in with supplying extra paramaters via DI::get (see the test_empty_slots test), which is functionality I'm finding quite useful - though only wrote the DI code yesterday so not exactly written in stone

    Have a good one,
    Douglas
    Attached Files Attached Files
    Hello World

  16. #41
    SitePoint Evangelist ghurtado's Avatar
    Join Date
    Sep 2003
    Location
    Wixom, Michigan
    Posts
    591
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by DougBTX
    Yes, so do I. Any idea how to pass a string by reference without getting a fatal error?
    Good point, I hadn't thought about that. Seems that we are tripped up by the funky world of PHP references yet once again. I suppose that until we come up with a better idea, two methods is what we have to deal with. I will think this through over the next few days though, I would like to find a workaround so that the interface can be simpler.

    Quote Originally Posted by DougBTX
    That could be put in, I left it out because it isn't something I need.
    I agree. After all, if the class depends on another one, passing the dependencies in the constructor seems like the logical choice. If we do this in the future it would only be to accommodate less than optimally written classes without the need to refactor them

    Quote Originally Posted by DougBTX
    It might be possible to make the array in DI::add associative, with the keys the setter method names, or add another method to set setters which can apply to objects comming from DI::set too.
    That is where I was coming from when I mentioned a "class signature class". It also provides a hook so that later on these class definitions can be made simpler, or automated (maybe code generation for php4, maybe reflection for php5, I don't know...)

    Quote Originally Posted by DougBTX
    Code:
    DI, for Dependency Injection. It is basically a registry, using
    DI::set and DI::get, with the added ability to create instances
    of classes using constructor injection. You can add constructor
    signatures using DI::add, then get new instances using DI::get.
    I pretty much agree with your definition of a registry on steroids.

    Quote Originally Posted by DougBTX
    I noticed you used registerSetter for both setter and constructor injection, is that a good thing?
    It depends. From the standpoint of the client, we may not wanna make the distinction between setter and construction injection, to simplify usage. After all, at the end of the day all I care about is that method X requires an object of class Y to be passed to it. This method could be a constructor or it could be something else.


    Quote Originally Posted by DougBTX
    Service seems too general but to specific to me, I'm not sure what it means here, so I'm hesitant to use it.
    I agree. I tend to gravitate towards the term "service" because the usage of the DI is similar to that of a service registry, in that in most cases you want to get the same instance of a particular class, no matter where you request it from, giving the impression of a centralized service. But either way, I am not too happy with the term. I guess I struggle with trying to find a "name" for that object you get back from the DI. I am glad we both realize the importance of naming things correctly, however.

    Quote Originally Posted by DougBTX
    You'll have to sell me on using type hinting / interfaces for this.
    I am not too fond of either type hints or interfaces in PHP5. Not too worried about it either, I guess we should just focus on the php4 version for now anyway. So no, I won't try to sell you either of those (at least not for now )

    I took a look at your code, and I like the way it looks. I'm still going around in circles trying to find a more semantic, more expressive way to define these meta attributes of the classes we register with the container. Although I know its impossible, I keep dreaming about Ruby closures, I was dazzled by the way this article handled such level of complexity with such elegance. Makes me wanna ask, just how evil is eval?

    Thank you for your interest, I feel that we could really build something useful out of this if we put our minds to it.
    Garcia

  17. #42
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by ghurtado
    Although I know its impossible, I keep dreaming about Ruby closures, I was dazzled by the way this article handled such level of complexity with such elegance.
    Why are we using PHP again?

    Seems like more time is spent doing stupid things like splitting add() and set() because of PHP bugs than actually doing anything useful...

    Douglas
    Hello World

  18. #43
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well, while we are coding in PHP, we might a well code in PHP.

    PHP Code:
    <pre><?php

    class FileLogger { function FileLogger $foo ) { } }
    class 
    Application { function run ( ) { echo '..running..'; } }

    class 
    Container {
        var 
    $things = array();
        function &
    get $something ) {
            if ( !isset(
    $this->registry[$something]) ) {
                
    $this->things[$something] = $something($this);
            }
            return 
    $this->things[$something];
        }
    }

    function 
    LogFile (&$c) { return "logfile.log"; }
    function 
    Logger (&$c) { return new FileLogger($c->get('LogFile')); }
    function 
    Application (&$c) {
        
    $application = new Application;
        
    $application->logger $c->get('Logger');
        return 
    $application;
    }

    $c =& new Container;
    $app =& $c->get('Application');
    $app->run();

    ?></pre>
    Even if it is as close a rip of the Ruby as I can get in a first try. I think PHP dulls the mind, and that Container should be a singleton. (In the Ruby version there is a ContainerRegistry, and it is the singleton. What's the code of the day for making singletons in PHP4/5 = PHP0.8 as I think I'll call it?)

    Douglas
    Hello World

  19. #44
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    And with a small addition, I can now use it as a factory and pass in my extra argument I was needing:

    PHP Code:
    <pre><?php

    class FileLogger { function FileLogger $foo ) { } }
    class 
    Application { function run ( ) { echo '..running..'; } }

    class 
    Container {
        var 
    $things = array();
        function &
    get $something$else null ) {
            if ( !isset(
    $this->registry[$something]) ) {
                
    $this->things[$something] = $something($this$else);
            }
            return 
    $this->things[$something];
        }
        function &
    now $something$else null ) {
            
    $this->things[$something] = $something($this$else);
            return 
    $this->things[$something];
        }
    }

    function 
    LogFile (&$c) { return "logfile.log"; }
    function 
    Logger (&$c) { return new FileLogger($c->get('LogFile')); }
    function 
    Application (&$c) {
        
    $application = new Application;
        
    $application->logger $c->get('Logger');
        return 
    $application;
    }

    $c =& new Container;
    $app =& $c->get('Application');
    $app->run();

    ?></pre>
    With the added now() it acts like a multipleton registry too (and only people in this forum would know the technical definition for that )

    You also don't want to be doing anything big in your constructors, as there will be ghosts running around in PHP0.8. Lazy Loading is Good for you.

    Douglas

    Update: still no unit tests, though there are tests:

    PHP Code:
    <pre><?php

    class FileLogger {
        var 
    $ms;
        function 
    FileLogger $file ) { /* ... */ }
        function 
    log ($m) { $this->ms[] = $m; }
    }
    class 
    Application {
        function 
    run ( ) { echo "..running..\n\n"; }
    }

    class 
    ContainerBase {
        function &
    get $something$new false ) {
            
    $things =& $this->_get_things();
            if ( 
    $new || !isset($things[$something]) ) {
                
    $things[$something] = $this->$something(func_get_args());
            }
            return 
    $things[$something];
        }
        function 
    set $something$thing ) {
            
    $things =& $this->_get_things();
            
    $things[$something] = thing;
        }
        function 
    set_ref $something, &$thing ) {
            
    $things =& $this->_get_things();
            
    $things[$something] =& $thing;
        }
        function &
    _get_things() {
            static 
    $things = array(); 
            return 
    $things;
        }
    }

    class 
    Container extends ContainerBase {
        function 
    LogFile ( ) { return "logfile.log"; }
        function 
    Logger ( )  { return new FileLogger($this->get('LogFile')); }
        function 
    Application ( ) {
            
    $application = new Application;
            
    $application->logger =& $this->get('Logger');
            return 
    $application;
        }
    }

    $c =& new Container;
    $app =& $c->get('Application');
    $app->run();

    print_r($app);

    $l =& $c->get('Logger');
    $l->log('hi there world');

    print_r($app);

    ?></pre>
    Last edited by DougBTX; Sep 9, 2005 at 17:30.
    Hello World

  20. #45
    SitePoint Evangelist ghurtado's Avatar
    Join Date
    Sep 2003
    Location
    Wixom, Michigan
    Posts
    591
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I am hoping you will like this.

    I have been going around in circles for a while about what might be the less entangled way to explain all these cross-dependencies in a clear way. Your post suggesting a function for each registered service or class was right on the money though, and it made me wonder, why not just create an anonymous function in the DI itself?

    So using your DI class as a base, I made some changes that let you do this:
    PHP Code:
    DI::register(
        
    'checkout'
        
    'return new MyCheckout($di->get("store"), $di->get("order"));');

    DI::register(
        
    'gateway'
        
    '$gateway = new MyPaymentGateway();
         $gateway->setOrder($di->get("order"));
         return $gateway;'
    );
        
    DI::register(
        
    'store'
        
    'return new MyStore($di->get("gateway"));');
        
    DI::register(
        
    'order'
        
    'return new MyOrder();');
        
    $checkout DI::get('checkout');
    echo(
    $checkout->execute());

    var_dump($checkout);
    var_dump(DI::_getInstance());

    // clear the object
    $di =& DI::_getInstance();
    unset(
    $di->registry); 
    I like the elegance of the solution, but more importantly, it covers the majority of our requirements
    • We have a single method for registering a service, and defining the constructor injection for a class, therefore it is all together in one line (or several, for readability)
    • We don't need to have two setter methods to overcome the issues with object references vs strings. As far as the DI is concerned, the constructor supplied is always a string anway.
    • It uses lazy loading, no objects instantiated until required
    • It uses an astonishingly simple way to figure out dependencies: it lets PHP sort them out. This is the sort of elegance in simplicity that I was praising in the Ruby article. There is no need for either type hinting, interfaces, signatures or metadata, since you are esentially just using a "code block". PHP does not reall have closures as you know, but it does have create_function which can be pretty neat at times.
    • It supports both constructor injection, setter injection, or even direct setting of attributes to inject its dependencies. Anything that works in PHP is valid.
    • It works in PHP4
    • The DI class is less than 60 lines of code (including comments), with only three methods

    Thanks for helping to move it in that direction, I knew you liked the idea of a simpler solution, with a little Ruby flavor

    Now, I know that some might see this as "cheating" and say that "create_function" is a bit like eval. I would counter that create_function is the closest thing in PHP to Ruby's closures, or that at least that it can be used in similar contexts, and personally, I think the style of programming that closures afford you usually means a much more expressive end result. So maybe changing the paradigm from time to time is not such a bad idea. In this case it provides us a tremendously simple solution to a potentially complicated problem.

    Let me know what you think, I am just throwing ideas out there, and maybe you don't agree with my take on it.
    Attached Files Attached Files
    Garcia

  21. #46
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by ghurtado
    Let me know what you think, I am just throwing ideas out there, and maybe you don't agree with my take on it.
    Ooh, I like, nice job!

    Quote Originally Posted by ghurtado
    Now, I know that some might see this as "cheating" and say that "create_function" is a bit like eval.
    The big difference being that it lets us reuse the evaluated code, because you can just put them in variables, nice. My biggest problem with them has been lack of syntax highlighting. Now though, I have to say I've been using a very nice text editor, and I feel I can bend its highlighting to my evil ways. All I need is to make "a single quoted string" look different from "a signle quoted string full of PHP code". Any ideas?

    I've added a line to DI::get ( $service =& $registry[$serviceName]; ) so that it is just $service, $service, $service rather than $registry[$serviceName], $registry[$serviceName], $registry[$serviceName], but really, it looks finished to me. Success

    Anything else anyone would like to add? Would this be good code to use in the "skeleton" thread anywhere? I've not been keeping track too much, but if anything, I like the coding style with this code much more than the style I've seen there.

    And it only took us two pages! Thanks for starting the thread heathd.

    Douglas
    Attached Files Attached Files
    Hello World

  22. #47
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Cool idea.

    The first question which comes to my mind is can a lambda return by reference? If not, you might have some trouble returning registered mockobjects effectivly.

    Oh, and if it can, don't forget your temporary vars
    Jason Sweat ZCE - jsweat_php@yahoo.com
    Book: PHP Patterns
    Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
    Detestable (adjective): software that isn't testable.

  23. #48
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by sweatje
    Cool idea.

    The first question which comes to my mind is can a lambda return by reference? If not, you might have some trouble returning registered mockobjects effectivly.

    Oh, and if it can, don't forget your temporary vars
    It's just accepting the facts of life and any ghost objects created on the way. The objects are getting cached in a static array, and all references point to that array. (The static array fills in the job of temporaries anyway, it is using the get_registry code you posted in the other DI thread.)

    Here's the code in question:

    PHP Code:
        /**
        * @return object
        * @param unknown $serviceName
        * @desc Get the instance referred to by the named service, or create one through 
        * the given constructor if one is registered.
        */
        
    function &get $serviceName ) {
            
    $di =& DI::_getInstance();
            
    $registry =& $di->registry;

            if ( isset(
    $registry[$serviceName]['object']) ) {
                return 
    $registry[$serviceName]['object'];
            }
            
            if ( isset(
    $registry[$serviceName]['constructor']) ) {
                
    $registry[$serviceName]['object'] = $registry[$serviceName]['constructor'](DI::_getInstance());
                return 
    $registry[$serviceName]['object'];
            }
            
            return 
    false;
        } 
    It just means that my "Lazy loading is good for you" comment above still applies.

    Douglas
    Hello World

  24. #49
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    To avoid the php4.4.0+ warnings and php5.1.0+ fatal errors, we would have to change to something like:

    PHP Code:
        function &get $serviceName ) {
            
    $di =& DI::_getInstance();
            
    $registry =& $di->registry;
            
    $ret false;

            if ( isset(
    $registry[$serviceName]['object']) ) {
                
    $ret =& $registry[$serviceName]['object'];
            }
            
            if ( isset(
    $registry[$serviceName]['constructor']) ) {
                
    $registry[$serviceName]['object'] = $registry[$serviceName]['constructor'](DI::_getInstance());
                
    $ret =& $registry[$serviceName]['object'];
            }
            
            return 
    $ret;
        } 
    Temp vars
    The code developing in this thread
    Jason Sweat ZCE - jsweat_php@yahoo.com
    Book: PHP Patterns
    Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
    Detestable (adjective): software that isn't testable.

  25. #50
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by sweatje
    To avoid the php4.4.0+ warnings and php5.1.0+ fatal errors, we would have to change to something like
    I'm not seeing any of that?

    $registry[$serviceName]['object'] is already a variable anyway, you don't need a temp variable to store a reference to a variable. The only time temps are a problem is when you have implied temps, return new Thing(), return call_something() and so on.

    I'm testing PHP 4.4.0 and PHP 5.0.4, so PHP 5.1.0 might be worse, but it wouldn't be from the implied temp var issues we've been seeing in other threads, this would be PHP not acknowledging $foo['bar'] to be a variable.

    Douglas
    Hello World


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
  •