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();
I was also playing with WACT controllers at this point (fall 04, IIRC). I think I would use more of the hierarchical controllers in post WACT-0.2 now.
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);
}
}
So there was my big secret
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() {
//...
}
}
and a model might need a database connection:
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;
}
}
I ended up using interfaces for all of the type hinting, so that I could use MockObjects in testing.
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;
}
//...
}
Bookmarks