SitePoint Sponsor |
|
User Tag List
Results 26 to 50 of 116
Thread: Dependency Injection in PHP4
-
Sep 7, 2005, 16:56 #26
- 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";
}
?>
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
-
Sep 7, 2005, 18:56 #27
- 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;
}
}
}
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;
}
}
}
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.
-
Sep 7, 2005, 19:19 #28
- Join Date
- Apr 2003
- Location
- London
- Posts
- 2,423
- Mentioned
- 2 Post(s)
- Tagged
- 0 Thread(s)
Hi...
Originally Posted by ghurtado
.
Originally Posted by ghurtado
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 { ... }
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";
}
?>
Originally Posted by ghurtado
.
Originally Posted by ghurtado
.
Originally Posted by ghurtado
PHP Code:class BankOfAmericaMoneyOrderTransfer implements MoneyOrderTransfer { ... }
PHP Code:case MONEY_TRANSFER:
$payment = $injector->create('MoneyOrderTransfer');
.
Originally Posted by ghurtado
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
-
Sep 8, 2005, 01:41 #29
- 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
}
}
}
And I'd also start wondering about the Checkout class itself at this stage... a class for one line of code?
DouglasLast edited by DougBTX; Sep 8, 2005 at 03:43.
Hello World
-
Sep 8, 2005, 02:41 #30
- Join Date
- Jan 2004
- Location
- Oslo, Norway
- Posts
- 894
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by lastcraft
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
-
Sep 8, 2005, 03:50 #31
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by dagfinn
The ServiceLocator :
PHP Code:class Foo
{
var $bar;
function Foo(&$components) {
$this->bar =& $components->createInstance('Bar');
}
}
...
$components =& new ServiceLocator();
$foo =& $components->createInstance('Foo');
PHP Code:class Foo
{
var $bar;
function Foo(&$bar) {
$this->bar =& $bar;
}
}
...
$components =& new DependencyInjector();
$foo =& $components->createInstance('Foo');
-
Sep 8, 2005, 03:54 #32
- Join Date
- Nov 2001
- Location
- Bath, UK
- Posts
- 2,498
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by kyberfabrikken
PHP Code:class Checkout {
function execute() {
$components = ServiceLocator::instance();
$store = $components->createInstance('Store');
if($store->submitOrder()){
return true;
} else {
return false;
}
}
}
DouglasHello World
-
Sep 8, 2005, 04:02 #33
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by DougBTX
PHP Code:$store = Components::CreateInstanceOf('Store');
That would all depend on the degree of flexiblity you want in your application.
-
Sep 8, 2005, 04:47 #34
- Join Date
- Jun 2003
- Location
- Iowa, USA
- Posts
- 3,749
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by lastcraft
-
Sep 8, 2005, 05:27 #35
- Join Date
- Apr 2003
- Location
- London
- Posts
- 2,423
- Mentioned
- 2 Post(s)
- Tagged
- 0 Thread(s)
Hi...
Originally Posted by dagfinn
.
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, MarcusMarcus Baker
Testing: SimpleTest, Cgreen, Fakemail
Other: Phemto dependency injector
Books: PHP in Action, 97 things
-
Sep 8, 2005, 09:15 #36
- 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.
Originally Posted by sweatje
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;
}
}
}
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');
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...
Originally Posted by DougBTX
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
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.
Originally Posted by DougBTX
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;
}
}
Originally Posted by lastcraft
Garcia
-
Sep 8, 2005, 11:18 #37
- Join Date
- Nov 2001
- Location
- Bath, UK
- Posts
- 2,498
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by ghurtado
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');
}
}
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;
}
}
Last edited by DougBTX; Sep 8, 2005 at 12:56. Reason: updates
Hello World
-
Sep 8, 2005, 13:21 #38
- 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
-
Sep 8, 2005, 15:58 #39
- Join Date
- Jun 2004
- Location
- London
- Posts
- 6
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by lastcraft
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.
Originally Posted by lastcraft
Dave
-
Sep 9, 2005, 05:32 #40
- Join Date
- Nov 2001
- Location
- Bath, UK
- Posts
- 2,498
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by ghurtado
Originally Posted by ghurtado
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.
Originally Posted by ghurtado
Originally Posted by ghurtado
Originally Posted by ghurtado
Have a good one,
DouglasHello World
-
Sep 9, 2005, 13:47 #41
- Join Date
- Sep 2003
- Location
- Wixom, Michigan
- Posts
- 591
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by DougBTX
Originally Posted by DougBTX
Originally Posted by DougBTX
Originally Posted by DougBTX
Originally Posted by DougBTX
Originally Posted by DougBTX
Originally Posted by DougBTX
)
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
-
Sep 9, 2005, 15:22 #42
- Join Date
- Nov 2001
- Location
- Bath, UK
- Posts
- 2,498
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by ghurtado
Seems like more time is spent doing stupid things like splitting add() and set() because of PHP bugs than actually doing anything useful...
DouglasHello World
-
Sep 9, 2005, 16:12 #43
- 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>
DouglasHello World
-
Sep 9, 2005, 16:31 #44
- 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>)
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
-
Sep 10, 2005, 01:47 #45
- 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);
- 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.Garcia
-
Sep 10, 2005, 04:43 #46
- Join Date
- Nov 2001
- Location
- Bath, UK
- Posts
- 2,498
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by ghurtado
Originally Posted by ghurtado
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.
DouglasHello World
-
Sep 10, 2005, 06:10 #47
- 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 varsJason Sweat ZCE - jsweat_php@yahoo.com
Book: PHP Patterns
Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
Detestable (adjective): software that isn't testable.
-
Sep 10, 2005, 06:16 #48
- Join Date
- Nov 2001
- Location
- Bath, UK
- Posts
- 2,498
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by sweatje
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;
}
DouglasHello World
-
Sep 10, 2005, 06:34 #49
- 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;
}
The code developing in this threadJason Sweat ZCE - jsweat_php@yahoo.com
Book: PHP Patterns
Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
Detestable (adjective): software that isn't testable.
-
Sep 10, 2005, 06:44 #50
- Join Date
- Nov 2001
- Location
- Bath, UK
- Posts
- 2,498
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by sweatje
$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.
DouglasHello World
Bookmarks