SitePoint Sponsor |
|
User Tag List
Results 1 to 25 of 39
-
Apr 23, 2005, 12:12 #1
- Join Date
- Apr 2003
- Location
- London
- Posts
- 2,423
- Mentioned
- 2 Post(s)
- Tagged
- 0 Thread(s)
The smallest dependency injector?
Hi.
As a result of an article for PHP|Architect I have finally gotten a chance to play with dependency injection and the result is called "Phemto". It's the bare minimum of a dependency injector as I understand it. I am sure that I have missed some vital feature that Jason, et al. will tell me about. It only works with PHP5.
Here is a sample piece of usage...
PHP Code:class Datacash implements PaymentGateway { ... }
class PayementInAdvance implements SignUpProcess {
function __construct(PaymentGateway $gateway) { ... }
...
}
$injector = new Injector();
$injector->register('Datacash');
$injector->register('PaymentInAdvance');
$sign_up = $injector->create('SignUpProcess');
.
yours, MarcusMarcus Baker
Testing: SimpleTest, Cgreen, Fakemail
Other: Phemto dependency injector
Books: PHP in Action, 97 things
-
Apr 24, 2005, 10:17 #2
- Join Date
- Nov 2002
- Posts
- 841
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
So what is the killer app for a dependency injection container in PHP. (versus manual dependency injection.)
I can't give you any more rep, Marcus.
-
Apr 24, 2005, 11:34 #3
- Join Date
- Apr 2003
- Location
- London
- Posts
- 2,423
- Mentioned
- 2 Post(s)
- Tagged
- 0 Thread(s)
Hi...
Originally Posted by Selkirk
.
Given that a PHP "program" is just a small slice of the application, the construction needs shouldn't be that great, right? No need for complicated wiring and all that? Well, I now have a kind of half idea that I may have been missing out on something.
The injector seems to be of most use for wiring up large chunks of application when the authors of these chunks are constantly changing their dependencies on each other. By giving each other interfaces (or writing adapters) you can gather together say a catalog, a store backend and a shopping cart without having to see the actual code at all. You don't have to see the constructors of these objects either and thus you know nothing of their dependencies as long as you can satisfy each one on demand.
Because of the constructor passing, the components don't know anything about the injector either, they just have to follow the rules for that injector. In the case of Phemto, everything in the constructor with a type hint.
Is this any use? I have no idea. I have never tried it or yet needed to.
It's currently just a curiosity to me. To make it useful in a real program anyway, you would have to add Injector::registerAsSingleton() and, especially for PHP, Injector::registerAsSession(). I would also want to allow superclass specifications, rather than just interfaces, for completeness. It's currently only 50 lines of code after all.
I do have a vague feeling it could be useful...
yours, MarcusMarcus Baker
Testing: SimpleTest, Cgreen, Fakemail
Other: Phemto dependency injector
Books: PHP in Action, 97 things
-
Apr 24, 2005, 13:09 #4
- Join Date
- Jan 2003
- Posts
- 5,748
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Off Topic:
I can't give you any more rep, Marcus.
But I can
-
Apr 25, 2005, 02:14 #5
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
the code looks impressively clean. i still haven't completely understood what the use for dependency injections is, but well ... seems to be a common problem though ?
-
Apr 25, 2005, 03:27 #6
- Join Date
- Dec 2004
- Location
- ljubljana, slovenia
- Posts
- 684
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Nice work, although it's not very clear to me, why if you register two classes (that implement the same interface) and then create the interface, you get the latter one. I mean, I see why it works like that, since in the register method, a class is saved in the hash with the interface name, so the latter class 'overrides' the former. What I don't understand is, why is it so? Does it have to be like that? Is constructing a class by passing the interface name to the injector usable at all?
Regards,
f
-
Apr 25, 2005, 04:43 #7
Originally Posted by dbevfat
Think I'd remove the dynamic code building/eval(), using ReflectionClass::newInstance()
$reflection = new ReflectionClass($class);
$object = call_user_func_array(array($relection, 'newInstance'), $objects);
Wouldn't this also need recursion protection, if a class deeper the construction depended on something higher? Probably shouldn't happen in a well designed application, but it'd be good to make sure.
-
Apr 25, 2005, 07:09 #8
- Join Date
- Jan 2003
- Posts
- 5,748
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Yer, I have a hard time following this as well, but maybe someone can gleam something more from this image I've attached?
I've proberly had the need for this but not realised it myself, what I could do with is more examples... Hint, hint
-
Apr 25, 2005, 12:59 #9
- Join Date
- Apr 2003
- Location
- London
- Posts
- 2,423
- Mentioned
- 2 Post(s)
- Tagged
- 0 Thread(s)
Hi...
Originally Posted by kyberfabrikken
Suppose the test script does this...
PHP Code:// At top...
$injector = new Injector();
$injector->register('Gateway');
// Later on...
$injector = new Injector();
$gateway = $injector->create('Gateway');
PHP Code:class TestOfGatewayPage extends WebTestCase {
function testStubbedOutGateway() {
$this->get('stub_gateway_registration.php');
$this->get('gateway_page.php');
...
}
}
PHP Code:$injector = new Injector();
$injector->registerInSession('StubGateway', 'gateway_stub.php');
$injector->create('Gateway');
PHP Code:class StubGateway implements Gateway {
...
}
Probably not a good example, but I do get the feeling that there is something to the DI stuff after all. I'm still groping. Other lifecycle options might be shared memory, or even automatic stubbing for the trick above. It's the way the lifecycle is defines outside of the client code which is the other half of the DI puzzle I think.
yours, Marcus
p.s. that is "Dependency Injection", not "Dependency Inversion".
Marcus Baker
Testing: SimpleTest, Cgreen, Fakemail
Other: Phemto dependency injector
Books: PHP in Action, 97 things
-
Apr 25, 2005, 13:09 #10
- Join Date
- Jan 2003
- Posts
- 5,748
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
What? They're not the same then?
-
Apr 25, 2005, 13:26 #11
- Join Date
- May 2003
- Location
- Calgary, Alberta, Canada
- Posts
- 275
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by lastcraft
Object creation isnt as simple as laid out in most DI examples. Usually creation of objects is hidden behind some sort of creational method. This means as already mentioned that the Injector would need to be a singleton (Kind of goes against the entire DI priciple) or passed into the object (Dont really like this idea either as it dirties the interface).
On top of being hidden behind factories objects are rarely constructed like this:PHP Code:$object = new Object(new ObjectB(), new ObjectC(new Object D()));
For some reason the idea seems to break down and become more trouble than its worth. I could be and hope Im wrong though, because the theory behind it should enable you to make dependency changes much easier, and thats always good.
-
Apr 25, 2005, 16:12 #12
- Join Date
- Apr 2003
- Location
- London
- Posts
- 2,423
- Mentioned
- 2 Post(s)
- Tagged
- 0 Thread(s)
Hi...
Originally Posted by Brenden Vickery
Does anyone have any experience using DI tools? Everyone (me included) seems to be judging something that we have no personal experience of.
yours, MarcusMarcus Baker
Testing: SimpleTest, Cgreen, Fakemail
Other: Phemto dependency injector
Books: PHP in Action, 97 things
-
Apr 25, 2005, 16:35 #13
- Join Date
- May 2003
- Location
- Calgary, Alberta, Canada
- Posts
- 275
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by lastcraft
. I have no experience using one. Hopefully we will all get a chance to use Pawel's port of pico soon though.
-
Apr 25, 2005, 17:18 #14
- Join Date
- Jan 2005
- Location
- Sydney
- Posts
- 43
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
I think it would be good in a system, where there may be a range of objects that focus on one task, run pretty much the same, but are different in their naming.
So instead of going through all of your code, you could just write two or three lines in the beginning that would create a register for say a class to communicate with the database.
This would also make it easier if someone had set up a pgsql and mysql class, but didn't write them in a way that they we're easily swapped with a configuration setting. Although the example isn't that good it does show when it can be a good thing to use something like DI.
I don't really see it in a situation where you change the dependency and this also breaks the code. But that's just my take on it.
-
Apr 25, 2005, 18:10 #15
- Join Date
- Nov 2002
- Posts
- 841
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by dylanegan
Global filters are good for setting up database objects, for example. You perform all the code to connect to the database and instantiate a db connection object. The way you get this to your Actions is by putting it in the $request object in the filter and retrieving it in your actions (see $request->setAttributeByRef(), $request->setAttribute() and $request->getAttribute()).
-
Apr 25, 2005, 18:29 #16
- Join Date
- Jun 2003
- Location
- Iowa, USA
- Posts
- 3,749
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by Selkirk
And "keeps cropping up" implies you have seen this more than once?!?
Originally Posted by Brenden Vickery
I think the DI pattern may make you want to put your thinking cap
on a little askew. Whereas when you are coding up things by hand, passing in to many different objects in the constructor is a bit of a pain, but when you ask for an object, which as another object which was constructed with two other objects from the DI container, and it just hands it to you, it puts things in a slightly different perspective.
You can see some of my efforts at combining WACT and Pico here. All I have used it for is to "auto-wire" the application to run, and allow for injection of MockObjects during testing.Jason Sweat ZCE - jsweat_php@yahoo.com
Book: PHP Patterns
Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
Detestable (adjective): software that isn't testable.
-
Apr 25, 2005, 21:18 #17
- Join Date
- May 2003
- Location
- Calgary, Alberta, Canada
- Posts
- 275
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by sweatje
I like putting my thinking cap on askew. Maybe you guys can help me here.
The example from Fowlers article is, as he admits, a naive example. "Super simple, small enough to be unreal". But Ill use it anyway. Here is his test case converted to php/simpletest using pico contatiner.PHP Code:class TestOfMovieLister extends UnitTestCase {
private function configureContainer() {
$pico = new DefaultPicoContainer();
$finderParams = array(new ConstantParameter("movies1.txt"));
$pico->registerComponentImplementation('MovieFinder', 'ColonMovieFinder', $finderParams);
$pico->registerComponentImplementation('MovieList');
return $pico;
}
public function testWithPico() {
$pico = $this->configureContainer();
$lister = $pico->getComponentInstance('MovieLister');
$movies = $lister->moviesDirectedBy("Sergio Leone");
$this->assertEquals("Once Upon a Time in the West", $movies[0]->getTitle());
}
}
PHP Code:class TestOfMovieLister extends UnitTestCase {
public function testWithoutPico() {
$lister = new MovieLister(new ColonMovieFinder('movies.txt'));
$movies = $lister->moviesDirectedBy("Sergio Leone");
$this->assertEquals("Once Upon a Time in the West", $movies[0]->getTitle());
}
}
If I had a pico expert beside me here is what I would ask:
- Am I supposed to be passing pico to all my objects like it is a huge factory?
- Am I supposed to be making multiple instances of pico all over my code?
- Am I only supposed to be using pico in my tests?
- How do I deal with objects that need to be created using objects that have already been constructed (eg. Not just class names but real objects that need to be passed into the constuctor).
-
Apr 25, 2005, 21:23 #18
- Join Date
- Nov 2002
- Posts
- 841
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by sweatje
Ever since that picoContainer thread, I've had a hunch that there was a dependency injection container hidden somewhere in the WACT Handle class. I just don't know where yet.
-
Apr 25, 2005, 21:56 #19
- Join Date
- Aug 2004
- Location
- California
- Posts
- 1,672
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
// At top...
$injector = new Injector();
$injector->register('Gateway');
// Later on...
$injector = new Injector();
$gateway = $injector->create('Gateway');Christopher
-
Apr 25, 2005, 22:06 #20
- Join Date
- Nov 2002
- Posts
- 841
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Possibly related: Singleton registry
-
Apr 26, 2005, 04:34 #21
- Join Date
- Jun 2003
- Location
- Iowa, USA
- Posts
- 3,749
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Unfortunatly, the need for the project I was working on with WACT and Pico evaporated before I completed the project, and a new project of similar complexity has not come along yet.
Here is where I got to (remember this is basically initial experimentation, not a well factored design).
a bootstrap file:
PHP Code:require_once 'setup.php'; //constants
session_start();
$pico = new DefaultPicoContainer;
ApplicationController::registerComponents($pico);
ApplicationController::processActions($pico);
$page_ctrlr = new ParameterDispatchController;
$page_ctrlr->setParameterName(VIEW);
ApplicationController::addPages($pico, $page_ctrlr);
$page_ctrlr->start();
Here was the ApplicationController. Static calls, so more of just a namespace thing:
PHP Code:class ApplicationController {
static function registerComponents($pico) {
$pico->registerComponentImplementation('AmpAdodbConn', 'AmpAdodbConn',
array('db' => new ConstantParameter(DBC)));
foreach(array(
// util
'Post',
// models
'CDPlan', 'FilterState',
// actions
'SetFilterState', 'UpdPlan'
) as $item) {
$pico->registerComponentImplementation($item, $item);
}
}
protected static function actionMap() {
return array(
'filter' => 'SetFilterState'
,'updplan' => 'UpdPlan'
,'splitplan' => 'SplitPlan'
);
}
static function processActions($pico) {
$post = $pico->getComponentInstance('Post');
if ($post->hasKey(ACTION)
&& array_key_exists($request = $post->get(ACTION)
,$map = ApplicationController::actionMap())
) {
$class = $map[$request];
if (!class_exists($class)) {
require_once 'actions/'.$class.'.php';
}
$action = $pico->getComponentInstance($class);
if (method_exists($action, 'process'))
$action->process();
}
}
static function addPages($pico, $page_ctrlr) {
$view_map = array(
'plan' => 'PlanView'
);
foreach($view_map as $view => $class) {
$pico->registerComponentImplementation($class, $class);
$page_ctrlr->addView($view, new PicoLazyProxy($pico, $class, 'views/'));
}
$page_ctrlr->setDefaultView(new PicoLazyProxy($pico, 'PlanView', 'views/'));
}
static function addGlobalTemplateVars($ds) {
$ds->set('action', ACTION);
$ds->set('appl_link', BASE_URL);
$ds->set('view_link', VIEW_URL);
$ds->set('action_link', ACTION_URL);
$ds->set('jsstart', '<script type="text/javascript">');
$ds->set('jsend', '</script>');
}
}
class PicoLazyProxy {
protected $pico;
protected $class;
protected $path;
protected $subject;
public function __construct($pico, $class, $path='') {
$this->pico = $pico;
$this->class = $class;
$this->path = $path;
}
protected function subject() {
if (!($this->subject instanceof View)) {
if (!class_exists($this->class)) {
require_once $this->path.$this->class.'.php';
}
$this->subject = $this->pico->getComponentInstance($this->class);
}
return $this->subject;
}
public function __call($method, $args) {
return call_user_func_array(array($this->subject(), $method), $args);
}
}
use a LazyLoading Proxy which remembers the PicoContainer.
So an action might need a model and the request:
PHP Code:interface iUpdPlan {
public function process();
}
class UpdPlan extends BaseCdplanAction implements iUpdPlan {
protected $cdp;
protected $post;
function __construct(iCDPlan $cdp, iRequestHash $post) {
$this->cdp = $cdp;
$this->post = $post;
}
public function process() {
//...
}
}
PHP Code:interface iCDPlan {
public function getPlants($requery=false);
public function getRfps($requery=false);
public function getPlan($filter=false);
public function update($plant, $rfp, $month, $wgt);
public function getAreSplits();
}
class CDPlan extends BaseCdplanModel implements iCDPlan {
//...
}
class BaseCdplanModel {
protected $conn;
function __construct(AmpAdodbConn $db) {
$this->conn = $db;
}
}
PHP Code:if (!class_exists('MockCDPlan')) {
Mock::Generate('CDPlan', 'IntermediateCDPlan');
class MockCDPlan extends IntermediateCDPlan implements iCDPlan {
public function getPlants($requery=false) {
parent::getPlants($requery);
}
public function getRfps($requery=false) {
parent::getRfps($requery);
}
public function getPlan($filter=false) {
parent::getPlan($filter);
}
public function update($plant, $rfp, $month, $wgt) {
parent::update($plant, $rfp, $month, $wgt);
}
public function getAreSplits() {
parent::getAreSplits();
}
}
}
class TestUpdPlan extends CdplanActionUnitTestCase
{
function TestProcess() {
$key = 'DPWA|-9999';
$post = $this->getPost(array(
'process' => array($key)
,$key.'|1' => '10,000'
,$key.'|2' => '12,000'
), true);
$post->expectCallCount('hasKey',14);
$cdp = new MockCDPlan($this);
$cdp->expectArgumentsAt(0, 'update', array(
'DPWA', '-9999', '200501', '10000'));
$cdp->expectArgumentsAt(1, 'update', array(
'DPWA', '-9999', '200502', '12000'));
$cdp->expectArgumentsAt(2, 'update', array(
'DPWA', '-9999', '200503', null));
$cdp->expectCallCount('update', 2);
$pico = $this->getPico($post, $cdp);
ApplicationController::processActions($pico);
$this->assertRedirect('cdplan.php');
$post->tally();
$cdp->tally();
}
protected function getPico($post=false, $cdp=false) {
$pico = new DefaultPicoContainer;
ApplicationController::registerComponents($pico);
if ($post instanceof iRequestHash) {
$pico->unregisterComponent('Post');
$pico->registerComponent(new InstanceComponentAdapter($post, 'Post'));
$this->assertIdentical($post, $pico->getComponentInstance('Post'));
}
if ($cdp instanceof iCDPlan) {
$pico->unregisterComponent('CDPlan');
$pico->registerComponent(new InstanceComponentAdapter($cdp, 'CDPlan'));
$this->assertIdentical($cdp, $pico->getComponentInstance('CDPlan'));
}
return $pico;
}
//...
}
-
Apr 26, 2005, 04:36 #22
- Join Date
- Jun 2003
- Location
- Iowa, USA
- Posts
- 3,749
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by Selkirk
-
Apr 26, 2005, 07:12 #23
- Join Date
- Jan 2003
- Posts
- 5,748
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
SweatJe, interesting concepts you have, indeed
I don't like the idea either of putting objects into the request, this I feel is a bit suspect in my mind.
Crufty java inspired implementation aside, perhaps this is part of the "killer application" for dependency injection.
-
Apr 26, 2005, 10:35 #24
- Join Date
- Apr 2003
- Location
- London
- Posts
- 2,423
- Mentioned
- 2 Post(s)
- Tagged
- 0 Thread(s)
Hi...
Originally Posted by arborint
yours, MarcusMarcus Baker
Testing: SimpleTest, Cgreen, Fakemail
Other: Phemto dependency injector
Books: PHP in Action, 97 things
-
Apr 26, 2005, 12:06 #25
- Join Date
- Aug 2004
- Location
- California
- Posts
- 1,672
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
They are closely related. Needle supplies both for example.
Given that Service Locator is described as both a subset of Dependency Injection and easier to implement, when are they applicable in PHP?
It seems that with the PHP Request/Response cycle only executing a small slice of the whole application that the disadvantages of Service Locator in that you need to pass the object around are minimal. Current PHP frameworks already pass a Request object about as many places. Why not just replace the Request object with a Service Locator object and at a minimum register the Request object with the Service Locator. Quick example:PHP Code:class Locator {
var $_cache;
function Locator() {
$this->_cache = array();
}
function setLib($name, &$lib) {
$this->_cache[$name] = &$lib;
}
function &getLib($name) {
return $this->_cache[$name];
}
function isLib($name) {
return ($this->getLib($name) !== null);
}
}
PHP Code:class Request {
var $_request = array();
function Request() {
if (!strcasecmp($_SERVER['REQUEST_METHOD'], 'POST')) {
$this->_request =& $_POST;
} else {
$this->_request =& $_GET;
}
if (! get_magic_quotes_gpc()) {
$this->removeSlashes($this->_request);
}
}
function removeSlashes(&$str) {
if (is_array($str)) {
foreach ($str as $key => $val) {
if (is_array($val)) {
$this->removeSlashes($val);
} else {
$array[$key] = stripslashes($val);
}
}
} else {
$str = stripslashes($str);
}
}
function get($name) {
return $this->_request[$name];
}
function set($name, $value) {
if ($name) {
$this->_request[$name] = $value;
}
}
}
PHP Code:class Controller {
function execute(&$locator) {
$request =& $locator->getLib('request');
// code here
}
}
PHP Code:$locator =& new Locator();
$locator->setLib('request', new Request());
$controller =& new Controller();
$controller->execute($locator);
PHP Code:$locator =& new Locator();
$locator->setLib('request', new Request());
$locator->setLib('db', new PostgressConnector('localhost', 'user'));
$locator->setLib('template', new Template('./templates'));
$controller =& new Controller();
$controller->execute($locator);
Christopher
Bookmarks