Care to explain a bit?
| SitePoint Sponsor |





Care to explain a bit?
Sure.Originally Posted by stereofrog
That's not an issue with php5 ofcourse.PHP Code:require_once('../simpletest/unit_tester.php');
require_once('../simpletest/reporter.php');
error_reporting(E_ALL);
function &single($klass) {
static $map = array();
if(!isset($map[$klass])) {
$e=(($c=count($a=func_get_args()))<2)?''
:'$a['.implode('],$a[',range(1,--$c)).']';
eval("\$map['$klass']=&new $klass($e);");
}
return $map[$klass];
}
// playtoys
// ids show that we have singletons
class App {
function App(&$db, &$logger) {
$this->id = rand();
$this->db = &$db;
$this->logger = &$logger;
}
function run() {
var_dump($this);
}
}
class DbLogger {
function DbLogger(&$db) {
$this->id = rand();
$this->db = &$db;
}}
class DB {
function DB($dsn) {
$this->id = rand();
$this->dsn = $dsn;
}}
// Config is able to handle everything by itself.
class Config {
var $dsn = "mysql://user:pass@host/db";
function &App() {
return single('App', $this->DB(), $this->Logger());
}
function &Logger() {
$l = &single('DbLogger', $this->DB());
// $l = &single('DbLogger');
// $l->db = &$this->DB();
return $l;
}
function &DB() {
return single('DB', $this->dsn);
}
}
class TestOfSingleton extends UnitTestCase
{
function TestOfSingleton() {
$this->UnitTestCase();
}
function test_referential_integrity() {
$conf =& new Config();
$app =& $conf->App();
$this->assertReference($conf->DB(), $app->db);
}
}
$test = new TestOfSingleton();
$test->run(new HtmlReporter());





Oh that's ill.I'm sure someone will come with the fix though...
![]()





Are you going to post new code with these ideas incorporated. ghurtado, what do you think about Doug's simplified register? Do we need the full code block for register, or just a class and parameters? The latter assumes that if you had a code block, you would wrap it in a class. Is this acceptable?Originally Posted by DougBTX
Christopher





I'm somewhat backtracking, the bulk of the code is back here on page two. Attached are the changes I've made with ideas from the thread. It even has something which pretends to be documentation.Originally Posted by arborint
It could be simplified if you removed the ability to pass in paramaters to get. I've simplified my application so that I can use the class as a ServiceLoactor which happens to inject dependencies behind the scenes if need be, so I've renamed it Container instead of DI. It works fine as a plain registry too, which is helpful for me. As I'm not using the static access anymore, so a short name isn't as important as it was. The syntax looks more like this:
The add method could be renamed reg or register, I didn't do it in the attached code because... I didn't have to. Still a shame about having to split add and set - silly PHP references. Any ideas on that?PHP Code:$c =& Container::getInstance();
$c->add('Checkout', 'MyCheckout', array('Store', 'Order'));
$c->add('Store', 'MyStore', array('Gateway'));
$c->add('Order', 'MyOrder');
$c->set('Gateway', $test_payment_gateway);
Edit: just added a simplified version without the null arguments support, and without autowrapping dependencies in an array, so you have to use an array even if you have only one dependency. It must be good, it has a KB less.
Edit 2: removed unneeded reference, and added needed reference
Douglas
Last edited by DougBTX; Sep 14, 2005 at 03:38.
Hello World





As I recall, something like this will let you get rid of set():
PHP Code:function register ( /* $name, $classname, $signature = array() */ ) {
if (func_num_args() >= 2) {
$args = func_get_args();
$this->registry[$arg[0]]['constructor'] = array(
'classname' => $args[1],
'signature' => $args[2]
);
}
}
Christopher
What ?Originally Posted by arborint
The ampersand before $array isn't needed. Not that it matters, but I though I had to point it out.PHP Code:function &call_constructor_array ( $type, &$array )





Originally Posted by arborint
Echos 'bar', doesn't seem to work with PHP4 references?PHP Code:class Klass
{
var $foo;
}
$klass = new Klass;
$klass->foo = 'bar';
function manipulate ( )
{
$args = func_get_args();
$klass =& $args[0];
$klass->foo = 'manipulated';
}
manipulate($klass);
echo $klass->foo;
Good point, thanks, removed (updating the attachments above...)Originally Posted by kyberfabrikken
Douglas
Hello World





Hi...
Did you look up the Phemto code? It works like that too. Whilst you almost have to implement DI to understand it, watch out the thread doesn't just go around in circles.Originally Posted by Travis S
yours, Marcus
Marcus Baker
Testing: SimpleTest, Cgreen, Fakemail
Other: Phemto dependency injector
Books: PHP in Action, 97 things
Hey Marcus,
Yes... I did check out the Phemto code briefly as well as the PicoContainer. My problem is when I'm trying to get something that's new I'm better off implementing it myself at least once... It's that old habit of not wanting to use code that I can't fully explain I guess.Originally Posted by lastcraft
And yes... My ObjectFactory does get a container passed into it to look up other dependencies which could get you in a segfault loop pretty quick. In my preview release, I don't worry about that as it's really up to the implementor to figure that out. I just want to give him plenty of rope to hang himself with![]()



So far it seems like the idea of also including the path to the class file has been pushed aside, which I think is best, since it seems to me it belongs in a different set of responsibilities.Originally Posted by arborint
With Douglas' latest revision, it looks like we are back to an implementation which requires two separate registration methods: add and set. Personally I don't think that the difference between the two is that clear in the naming, but the issue could be solved by allowing add (or "register", I am still inclined to believe that name is more adequate to the purpose of the method) to receive objects as well as class names. After all, most of the time that you want to pass an object, I imagine you would call it as:
Since you are creating a new one, you wouldn't have a problem with references. Otherwise, if you plan to pass an object that was already preexisting, perhaps because you use the same one in another component registration call, then you would probably be better off by defining that third object as a component too anyway.PHP Code:$c->add('Checkout', 'MyCheckout', array('Store', new MyOrder()))
The term "add" always makes me think of linear lists.
If I were to see the above code in some foreign library I would probably think that three things just got added to some list.PHP Code:$c->add('Checkout', 'MyCheckout1');
$c->add('Checkout', 'MyCheckout2');
$c->add('Checkout', 'MyCheckout3');
Another smaller issue that I see is that by going from create_function to eval(), now the "Eval'd" code is interpreted everytime that a new component is instantiated instead of only once during the registration call, though I am not sure what type of performance impact this may have.
At the end of the day, I too prefer an API that allows you to work with parameters and arrays at registration time, instead of code blocks; I just wonder if for all the situations where you may need DI (many times dealing with 3rd party libraries which you would rather not rewrite) can be accounted for in Douglas' version. How would you do setter / property injection? I think this is an important point to allow the DI container to work with any preexisting Plain Old PHP Object.
Garcia





I think it is a trivial amount of additional code to make true Lazy Loading and not require some other mechanism such as __autoload(). But up to you guys.Originally Posted by ghurtado
Why not just call it what it isOriginally Posted by ghurtado
PHP Code:$c->register('Checkout', 'MyCheckout', array('Store', new MyOrder());
// and
$c->registerObject('Checkout', new MyCheckout('Store', new MyOrder()))
Christopher





Unfortunately, you lost all track of referential integrity.Originally Posted by DougBTX
PHP Code:class A {
function A(&$b) {
$this->b = &$b;
}}
class B {}
$c = &Container::getInstance();
$c->add('A', 'A', array('B'));
$c->add('B', 'B');
$aa = &$c->get('A');
$b0 = &$aa->b;
$b1 = &$c->get('B');
$b0->x = 99;
assert($b1->x == 99); // :((((
As said, actually no scaffolding code is necessary (except not-that-tiny singleton wrapper)It must be good, it has a KB less.
PHP Code:// carping permitted
function &single($klass) {
static $map = array();
if(!isset($map[$klass = strtolower($klass)])) {
array_shift($a = func_get_args());
foreach($a as $k => $v)
if(is_object($v) && isset($map[$c = strtolower(get_class($v))]))
$a[$k] = &$map[$c];
$e = '';
if($c = count($a))
$e = '$a[' . implode('],$a[', range(0, $c - 1)) . ']';
eval("\$map['$klass']=&new $klass($e);");
}
return $map[$klass];
}
PHP Code:class Conf {
function &A() {
return single('A', $this->B());
}
function &B() {
return single('B');
}
}
// A and B see above
$c = new Conf;
$aa = &$c->A();
$b0 = &$aa->b;
$b1 = &$c->B();
$b0->x = 99;
assert($b1->x == 99); // :)





Simplification by changing the problem, can't complain too much about that(and I've taken your better implode code too!)
Patch this:
becomes:PHP Code:$object =& call_constructor_array($classname, $dependencies);
return $object;
Or you don't get the same objects back out again. Thanks for the testcase - got any more?PHP Code:$sevice['object'] =& call_constructor_array($classname, $dependencies);
return $sevice['object'];
Cheers,
Douglas
Hello World





Yeah, that works now, good job.
Care to explain? What exactly "Container" code can and "Conf" code cannot?Simplification by changing the problem





The main thing for me me is that the only things you can put in the "Conf" registry (== $map) are instances of objects which have had their dependencies injected, you can't have any objects in there that were constructed somewhere else.
That means you can just have the static $map; in the same function as where you gather dependencies, shaving off a few lines of code, which is a good thing if you don't want to use $map as a normal registry.
Keeping that $map tied up also makes it harder to have the clear() method, and you can't have multiple instances of the registry. Again, both of those let you shave off a few lines of code.
The last thing that affects me is that it makes it harder to have multiple sets of "Conf" code. If you register something in one place, and then register something which depends on it later, you'll have to use different code to resolve those dependencies. It is scaffolding code that you've got to add each time you need it, rather than something built into the container.
I consider the bits in red to be scaffolding I'd rather not have, though you do need to have some, so I'd like as little as possible:
Code:class Conf { function &A() { return single('A', $this->B()); } function &B() { return single('B'); } }Code:$c = &Container::getInstance(); $c->add('A', 'A', array('B')); $c->add('B', 'B');Though only the second syntax lets me have multiple instances of the container.Code:DI::add('A', 'A', array('B')); DI::add('B', 'B');
Douglas
Hello World
Bookmarks