So I've read some already existing questions here (like this and this) and also I'm reading a book, but I still can't decide. Sorry for the long post, I've posted a decent amount of code here.
The routing, controller creating and action calling are working right now. Until this time I've just echo'd a simple text in the controller's action to see if it's working.
After this I've introduced a simple View class with a render() function which returns the "renderable" HTML code:
public function render()
{
if (!isset($this->file)) {
return "";
}
if (!empty($this->data)) {
extract($this->data);
}
// dirty-hack: use output buffering so we can easily check if a file can be included
// if not, simply reject the output and throw an exception (let the caller handle it)
// if the inclusion was successfull then return with the buffered content
ob_start();
if (!include($this->file)) {
ob_end_clean();
throw new \Exception("View file \"{$this->file}\" not found");
}
return ob_get_clean();
}
The Controller class also have a render() and a protected setView() functions
public function render()
{
if (isset($this->renderView)) {
echo $this->renderView->render();
}
}
protected function setView($view)
{
if (!is_object($view) || !($view instanceof View)) {
throw new \Exception("The \"view\" parameter have to be a View instance");
}
$this->renderView = $view;
}
An example controller with an example action:
public function viewHome()
{
$contentView = new Framework\View("HomeView.php");
// fill contentView with variables, using $contentView->setData("name", value)
$this->generateView($contentView);
}
protected function generateView($contentView)
{
if (!is_object($contentView) || !($contentView instanceof Framework\View)) {
throw new \Exception("Invalid \"contentView\" parameter");
}
$layout = new Framework\View("MainLayout.php");
$layout->setData("content", $contentView->render());
$this->setView($layout);
}
So actually the controller's action is assigning data to the view (Edit: actually it's not a view, more like a template, but it's my naming convention) and the view simply uses the data. This could be "reversed" so I could create View subclasses which would get a controller reference and use the controller to get/set the data to/from the presentation. For some reason I stick with the first (current) solution but I can change my mind. :)
The corresponding MainLayout.php test file is the following
<div style="margin-left: auto; margin-right: auto; padding: 5px; width: 800px; border: 3px solid green;">
Main header <br />
<br />
Content: <br />
<?php
if (!isset($content)) {
die("The \"content\" variable is not available");
}
echo $content;
?>
</div>
Note that the codes I've posted serve test purposes, things can be different later on. However my real problem comes with the model layer. I've read two different approach about the MVC pattern:
The first says that the model is a "stupid" layer, a simple memory/object representation of the data. The real work (so the DAO access, queries, business logic) is in the controller.
The other one says that the controllers are the "stupid" things, they are just a glue between the model and the view, the work is done by the model. The second one seems to be easier and it fits more to my current design. What do you think, which one is the preferred approach?
And another one: let's say I've choosen the 2nd approach (described above), so I have a model class for eg. a User. This class have different (static) functions for querying things, like "get all users", "add new user", and so on. However I don't want to connect the model with the Database this strong. My first idea is that I should create (at least) 3 classes for a single model:
- an abstract class/interface which defines the "model" itself
- at least one subclass (the implementation) for each data-access type
- and a factory for the model which instantiates the proper class. If we need a mock model for testing, it would return a UserMock instance instead of a UserDB instance.
Do you have any better ideas?
Edit:
Based on the accepted answer, I've decided to use the service design. Here is a simple (sample) class:
abstract class UserService
{
private static $instance;
public static function setInstance($instance)
{
if (!is_object($instance) || !($instance instanceof UserService)) {
throw new \Exception("Invalid \"instance\" parameter");
}
self::$instance = $instance;
}
public static function get()
{
if (!isset(self::$instance)) {
throw new \Exception("Instance is not set");
}
return self::$instance;
}
public abstract function findAll();
public abstract function findById($id);
public function add($user)
{
if (!is_object($user) || !($user instanceof User)) {
throw new \Exception("Invalid \"user\" parameter");
}
$id = $this->addAndReturnId($user);
$user->setId($id);
}
protected abstract function addAndReturnId($user);
}
This way I can register a service implementation (which uses the database or just filled with test data). However for each service I should copy-paste the set/get code. I could use a base class for this but the "instanceof" check is different for each subclass.
I know this is a bit off-topic but do you have any good idea for this? Or should I copy-paste that two functions for each service?