Thanks in advance for any insight. All used classes are at the top, and everything starts at the comment:
// where the magic happens
In particular, I am looking for feedback on my attempt at using the factory method and dependency injection. However, I would appreciate any other feedback as well. I have a list of various questions at the bottom.
<?php
class Config {
// Class that holds config info.
// Besides PDO connection info, contains flags for testing mode, live, eCommerce-enabled, etc.
// no setters, only getters... I'm thinking of this class as a glorified associative array
protected $properties = array();
function __construct()
{
// reads app config file(s), sets various keys in $this->properties;
}
public function get($key)
{
return $this->properties[$key];
}
}
class Request {
protected $server;
protected $get;
protected $post;
function __construct(array $server, array $get, array $post)
{
$this->server = $server;
$this->get = $get;
$this->post = $post;
}
// other methods in here like getUri, isAjax,
// getRequestMethod, getPost, getGet, getAgent, getRemoteAddr
}
class Session {
function __construct()
{
// Start the session
session_start();
// Set a user ID cookie, etc
}
// other session-related setters and getters
}
class ModelFactory{
protected $className; // class to instantiate (string)
function __construct($className)
{
$this->className = $className;
}
public function build(Config $c, Request $r, Session $s)
{
$pdo = new PDO(
$c->get('dsn') ,
$c->get('pdo_user') ,
$c->get('pdo_pass'),
$c->get('pdo_options')
);
return new $this->className($pdo, $c, $r, $s);
}
}
class ControllerFactory{
public function build()
{
$c = new Config();
$r = new Request($_SERVER, $_GET, $_POST);
$s = new Session();
// Reads config file with route info, compares it to $r->getUri
// to find name of controller and action within controller.
$name = 'Controller_Example';
$action = 'action_showComments';
// Returns correct child object of Controller.
return new $name($c, $r, $s, $action);
}
}
abstract class Controller{
protected $config;
protected $request;
protected $session;
protected $action;
function __construct(Config $c, Request $r, Session $s, $action )
{
$this->config = $c;
$this->request = $r;
$this->session = $s;
$this->action = $action; // string of name of function to execute
}
protected function before() { /* some extendable code to execute before action */ }
protected function after(){ /* some extendable code to execute after action */ }
public function execute()
{
// would an output buffer be good here? eg. ob_start()
// only doing this because im not sure if $this->$this->action() would work
$method_to_execute = $this->action;
$this->before();
$this->$method_to_execute();
$this->after();
}
}
class Controller_Example extends Controller{
/**
* A function that gets list of recent comments.
*/
public function action_showComments()
{
$m_factory = new ModelFactory('Model_Comment');
$comment_model = $m_factory->build($this->config, $this->request, $this->session);
$comments = $comment_model->getComments();
$title = 'Displaying Comments';
$bid = 'comment';
include '/path/to/views/commentview.php'; // see below for commentview.php
}
}
class Model_Comment {
protected $config;
protected $request;
protected $session;
protected $pdo;
function __construct(PDO $pdo, Config $c, Request $r, Session $s, )
{
$this->config = $c;
$this->request = $c;
$this->session = $c;
$this->pdo = $pdo;
}
public function getComments()
{
// uses $this->pdo to query database, returns an array of comments
// may or may not use request, session, or config objects
return array('This is a comment', 'So is this', 'And this is too!');
}
}
// where the magic happens
$factory = new ControllerFactory();
$controller = $factory->build();
$controller->execute();
?>
<!-- commentview.php -->
<!DOCTYPE html>
<head>
<title><?php echo $title ?></title>
</head>
<body id="<?php echo $bid ?>">
<ul>
<?php
foreach ($comments as $comment) {
echo '<li>'.$comment.'</li>';
}
?>
</ul>
</body>
<!-- end commentview.php -->
Questions:
Does
$this->$this->action()
work correctly if$this->action
is a string which is the name of a method in the same class (seeController::execute()
) ?If not every model needs the config, request, and session objects, does
ModelFactory::build()
violate the law of demeter? How can I avoid this problem?As a corollary, is there a way I can make the parameters for
Factory::build()
variable in order to have an abstract class or interface for all factories? Example:abstract class Factory{ abstract function build({variable params}) }
Won't having
new
operators for factories in places likeController_Example::action_showComments()
defeat the purpose of DI making code more testable?How could I approach templating html pages in this app?
What are some advantages for using
ob_start()
in a situation likeController::execute()
?