Controllers and Actions in MVC

I’d like to know how to properly create controllers and actions in MVC. So far I’ve used them in implementations that were not true MVC so I may not have a correct understanding about when I should create a new controller and when a new action - I’d like to learn some proper basics.

Let’s say I have some common tasks with managing users:

  1. Add user
  2. Edit user
  3. List users
  4. Delete users, hide/unhide users (toggle a flag on or off)
  5. Display single user data

Below I’m presenting my suggested scenarios, please comment on each one whether it looks good on not:

Pt. 1 & 2: adding and editing a user are almost identical in html display (same form) so I suspect these will require one controller with two actions. One controller because one controller is always tied to one view. Additionally, I think I will need submit actions that will get values submitted by POST and either redirect to another page or redisplay the form in case of a validation error. So I end up with 4 actions:


class UserFormController {
	
    public function add() {
        // ...
    }

    public function edit() {
        // ...
    }

    public function addSubmit() {
        // ...
    }

    public function editSubmit() {
        // ...
    }
}

Pt. 3: Listing users - there will be features like listing all users, filtering by criteria and pagination. I suspect I can get away with 1 controller with 1 action and simply pass all the necessary parameters from GET to the model in the action.

Pt. 4:: Deleting (selected) users, hiding/unhiding users - this is supposed to delete the users or perform some other operation in the db on the user and redirect back to the user list. Do I need the view at all in this case? I suppose I can create one controller with 3 actions:


class UserChangeController {

    public function delete() {
        // ...
    }

    public function hide() {
        // ...
    }

    public function show() {
        // ...
    }
}

What do I do about the view then? Do I create an empty one or allow my system to have viewless controllers? I’m a little baffled with this one - do I use these actions to pass POST criteria to specific methods in the model that will perform the requested updates on the users in the db? Like this:


class UserChangeController {

    public function delete() {
        $userIds = array_keys($_POST['checkboxes']);
        $this->model->deleteUsers($userIds);

        // I don't know if this is the best place to do redirection?
        header("Location: " . $_POST['redirect']);
        exit;
    }
}

Pt. 5:: a page which displays user info - this should be easy, 1 controller and 1 action.

Well… first off, to avoid any debates, we need to clarify which “kind” of MVC you want to implement. Instead of retyping it all, see one of my past posts.

In this case I want to implement MVC in its purest traditional sense, the one TomB is advocating.

How you actually structure the individual actions is application specific and not specified by MVC itself. However, here’s how I do adding/editing. They are essentially identical. I have my model look like something like this:


<?php
class UserEditController {
	private $user;
	private $userMapper;

	public function __construct(UserMapper $userMapper) {
		$this->userMapper = $userMapper;
	}	

	public function setEditing($id) {
		$this->user= = $this->userMapper->findById($id);				
	}
	
	public function getUser() {
		return $this->user;
	}

	public function save($data) {
		if ($data['id'] == null) {
			//Adding
		}
		else {
			//Editing
		}
	}


}

The controller then does this:


<?php
class UserEditController {
	private $model;


	public function __construct(UserEditModel $model) {
		$this->model = $model;
	}

	//Default action
	public function main($id = null) {
		$this->model->setEditing($id);
	}

	public function submit() {
		//Do some sanity checks on $data then send it to the model for saving
		$this->model->save($_POST['user']);				
	}

}

The view populates the form from $model->getUser() if $model->getUser() doesn’t return null. This way everything is reused. In actual fact, because of the way my DataMapper works (by using ON DUPLICATE KEY UPDATE) I never need the if/else for detecting adding/editing but that’s an implementation detail!

Listing users - there will be features like listing all users, filtering by criteria and pagination. I suspect I can get away with 1 controller with 1 action and simply pass all the necessary parameters from GET to the model in the action.

Possibly, strictly speaking these should all be separate actions but to achieve combinations your router would need to handle calling multiple actions on one request (Which isn’t a bad thing) but that’s quite a big topic in itself. The simplest way, with a URL:Controller action router is, as you say, having the controller parse $_GET and set the model’s state accordingly. The model should expose methods for sort/filter/paginate and the controller would call each of them.

Pt. 4:: Deleting (selected) users, hiding/unhiding users - this is supposed to delete the users or perform some other operation in the db on the user and redirect back to the user list. Do I need the view at all in this case? I suppose I can create one controller with 3 actions:

On which view does the “delete” button sit? If it’s the user list, then the UserList controller should have an action called “delete” which calls a (reusable!) model method which contains the implementation. Any place you have a delete user button would call the model’s action. On the redirect, you’ll send someone to a different page. Perhaps one that sates “The following users have been deleted: x,y,z”. That view could then include the list view again to show the updated list, if you so wished. That “Thank you” view may not have a model and almost certainly wouldn’t need a controller.

Pt. 5:: a page which displays user info - this should be easy, 1 controller and 1 action.

Not necessarily. Depending on what it’s doing. If it’s purely display then you don’t need a controller, however if you need to get the ID of the user being edited from $_GET then you need a controller for that. It seems redundant to have a controller with one action that just sets one value on the model but this is where the power of MVC actually comes in; the model’s state can be set from anywhere so another part of the system could pull in the model and view from another view without needing the controller.

Tom, thanks for your code examples. If MVC does not specify the structure of actions then it’s even more valuable to see some real world examples.

Yes, it’s purely display but the user ID sits in the URL of the page so is it possible to do without a controller? I think I need a controller to pass GET to the model’s state, is this correct?

Another thing, even if this was a static page that didn’t need any parameters from the URL (for example, a contact page) how would I do without a controller? A page like that needs its own URL (unless I’m including the view’s content somewhere else), which equals a fresh page request. If we assume that each page request initiates the MVC in this way:


model = new Model();
$controller = new Controller($model);
$view = new View($controller, $model);
echo $view->output();

then it’s clear that the page needs a controller because the view requires it. How is it then that I don’t need a controller? What am I missing here?

Yes, it’s purely display but the user ID sits in the URL of the page so is it possible to do without a controller? I think I need a controller to pass GET to the model’s state, is this correct?

Yes that’s correct. That’s a controller action, which then tells the model what user is being dealt with.

A view only needs a controller if the view has user interaction (or in our case, links/form submissions). These need to link back to a specific (interchangeable) controller. In the real world, to make these actions interchangeable, your view would also need access to a router, which worked out the route for a given controller action (The inverse of what the router does on the request, taking a url and routing to a controller action, in the view the router would take a controller and an action name and generate a URL for it).

If there are no actions, the view doesn’t need to request the controller in its constructor.

However, “Load the the user with the ID 123 into the view” is a user action if it’s coming from a URL so requires an action.

So basically, for static pages can I do something like this


$model = new Model();
$view = new View(null, $model);
echo $view->output();

?

To go even further, if I wanted to have an even simpler static page, could I do without the model as well?


$view = new View(null, null);
echo $view->output();

The model provides data to the view but if the view only outputs some simple html and doesn’t require any data from the model then would it be a good idea to allow views without a model? I suppose this would not be MVC anymore, but perhaps an MVC system could allow to be a V system on some requests?

Well even better:


$view = new View();
echo $view->output();

The view shouldn’t ask for parameters if it’s never going to need them. However, if the view can optionally have a model/controller then yes it makes sense to ask for them. It depends what the view is doing, but it should never ask for constructor arguments if it’s never going to use them.

Edit: Realistically though, for a “static page” you’ll probably define it in this kind of way so that it’s reusable.


class StaticView {
	private $model;

	public function __construct(StaticModel $model) {
		$this->model = $model;
	}

	public function output() {
		return $this->model->getStaticPage();
	}
}

You could then optionally have a controller which set a state on the model saying which page to load. The model could then load the page from somewhere. It’s a good example of MVC in action actually: The model is interchangeable and could then be substituted to load the static HTML from the filesystem, a database, a web service etc.

Edit2: And before someone says “Well the view is redundant then!” the view could be substituted to one which does some post-processing on the HTML that’s been received.

Thanks, this makes sense and the StaticPage view looks quite convenient!

One more question, on your site you provide an example where UserEditController and UserListController extend Controller and it’s not really clear why you are extending Controller. I suppose Controller is some kind of default controller but I can’t see anything in it that is actually inherited and used. And if controllers are supposed to be minimalist is it worthwhile creating hierarchies where one extends the other?

I’ll amend the example there, occasionally it’s useful for a controller to extend a base class but it’s certainly not necessary and in the case it isn’t. I’ll make the example more clear.

Okay, that’s great! Thanks for your answers, Tom, they are very instructive.

[ot]If I may have a suggestion for your site, it would be great if your code examples were actually tested (not unit-tested but simply run by you to check if they execute fine). I tried getting to work your Dice for routing as you explained it but the code fragments had a few typos I had to correct and in the end Dice errored out not being able to find a component whose name started with ‘$route_’ (xdebug pointing to line 33). Probably these are trivial things to correct by someone who knows how these patterns work but a bit more difficult for someone who is just learning them. I’ll get to play with DI for routing maybe at some later point so I’m not asking you for help with this now, I just want to make a general suggestion. Also, the working example of [URL=“http://r.je/mvc2.php?routecontroller”]Router is not working. Apart from that you’ve got a lot of useful information on yout site! :slight_smile:
[/ot]

Sorry, I just have to ask one quick question.

Presumably getStaticPage() is returning HTML. Why are we delegating presentation to the model?

My guess is that HTML returned by getStaticPage() comes from a data source like a database. So the view is actually using the model to get data from the database, the model is not creating the presentation. We can imagine that the admin panel may have a rich text editor for the page content. This can be looked upon as incorrect because we are throwing HTML into the db and then we are using the value, which is a mix of presentation and content (and possibly some behaviour in javascript) but in the real world this is often the most practical solution. TomB may correct me if I’m wrong on this :slight_smile:

I suppose I didn’t notice that we were assuming that the static HTML was stored in a database.

We’re not assuming anything, that’s the point. The model could get the HTML from anywhere, the view doesn’t care. MVC provides a basic method of applying proper Separation of Concerns to your code. Should a model load HTML? Sure, why not! It is just data after all! A model almost certainly shouldn’t be generating HTML on the fly, but loading data is loading data, the model doesn’t care whether that data is HTML or any other format or what the view is going to use it for.

Answer this: Should the model load a CSV file? An XML file? Obviously, the answer is yes. A HTML file is no different. You wouldn’t question a MVC set up where the model loaded a CSV file and the view outputted some HTML using data from the CSV (via the model). Why question a MVC set up where a model loads an HTML file and outputs a CSV (or in this case, outputs HTML)? The separation of concerns is the same; as long as the View doesn’t know the implementation of the model and the Model doesn’t care what the view does with the data then we’re all good!

You may wish to load the HTML and extract some specific data from it prior to displaying it, you may wish to display it as is. The goal here is to separate the loading and processing of the data. This allows us to use the same processing logic on data from multiple sources or apply different processing to data from the same source.

Well, actually, I would think that the model would provide only the data. It’s the view that would present that data as CSV or XML or JSON… or as HTML.

One of the reasons why I think model can’t have the restriction to load only raw data is that sometimes you really don’t have access to raw data. For example, you want to extract information from a separate server and you only have access to their HTML pages. In this case you have no choice other than load formatted data.

Edit: on second thought… maybe my example wasn’t the best one because it seems you think that the model should only provide raw data to the view regardless of how the data is fetched from the outside…

The model must get its data from somewhere. The point I was trying to make is that the View shouldn’t be concerned with the initial format (or location) of the data it’s going to use. The model could load data from a web service, database, CSV file, XML file or a binary signal intercepted between satellite dishes and the View would just read data in some data in a predefined format from the model. Whether that format is a string, an array, an object or anything is the contract between the model and the view and the only thing the view needs to know about the model. “I’ll get data in this format from this model API call”. How that data was constructed is of no concern of the views. The view is then free to present that data in any way it likes irrespective of what format and where the model happened to get the data initially.

I’ll provide a practical example, because it’s probably easier to follow. I won’t use HTML as an output format as that’s too weighted in PHP and makes things less obvious.


interface TomsModel {	
	public function getData();
}

interface TomsView {
	public function output();
	public function getMimeType();
}

Let’s assume getData() provides a key => value array of some arbitrary data and view::output() returns a string and getMimeType() returns the MIME type of the data in output();

I can now define some views to display the data in a variety of formats:


class JSonView implements TomsView {
	private $model;

	public function __construct(TomsModel $model) {
		$this->model = $model;
	}

	public function output() {
		return json_encode($this->model->getData());
	}

	public function getMimeType() {
		return 'application/json';
	}
}


class CsvView implements TomsView {
	private $model;

	public function __construct(TomsModel $model) {
		$this->model = $model;
	}

	public function output() {
		$stream =  fopen('php://temp/');
		foreach ($this->model->getData() as $row) {
			fputcsv($stream, $row);	
		}
		return stream_get_contents($stream);
	}

	public function getMimeType() {
		return 'text/csv';
	}
}



//Using the pchart library to display the data as a pie chart: http://pchart.sourceforge.net/documentation.php?topic=exemple15
class PieChartView implements TomsView {
	private $model;

	public function __construct(TomsModel $model) {
		$this->model = $model;
	}

	public function output() {
		$dataSet = new pData;
		foreach ($this->model->getData() as $row) {
			$dataSet->addPoint($row);
		}
		$dataSet->addAllSeries();

		$chart = new pChart(300, 300);	
		$chart->drawBasicPieGraph($dataSet->GetData(), $dataSet->GetDataDescription(),120,100,70,PIE_PERCENTAGE,255,255,218);  
		return $chart->render();
	}

	public function getMimeType() {
		return 'image/png';
	}
}

The beauty of this is that these views could work with any of these models:


class DatabaseModel implements TomsModel {
	private $pdo;

	public function __construct(PDO $pdo) {
		$this->pdo = $pdo;
	}

	public function getData() {
		return $this->pdo->query('.....');
	}
}



class CSVModel implements TomsModel {
	private $csvFile;

	public function __construct($csvFile) {
		$this->csvFile = $csvFile;
	}

	public function getData() {
		$returnData = array();
		if (($handle = fopen($this->csvFile, 'r')) !== false) {
    			while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
        			$returnData[] = $data;
			}

       		}
    		fclose($handle);

		return $returnData;			

	}
}


class SatelliteInteceptorModel implements TomsModel {


	public function __construct(SatelliteStream $statelliteStream) {
		$this->satelliteStream = $statelliteStream) ;

	}

	private function decode($data) {
		//...
	}

	public function getData() {
		$data = $this->decode($this->satelliteStream->interceptData);

		//format $data into an array that the view is expecting
		return $data;
	}

}


Any of these models can be used with any of the views. This is where MVC shines because the data source is entirely disconnected from the display logic. The model provides the view with some data in a format they both know about. The view does whatever it likes to this data in order to display it and the model takes data from any source and puts it into the format that the view can use. This makes the display logic reusable because it can be used with any of the models and it means a new model can be added without having to write a new view for it.

For example, I could write a new model which combined data from two separate CSV files and still use the same view.

As you see, it’s possible to go from CSV -> CSV using my example code and have a lot of redundant processing but, this redundancy is what allows the view to be agnostic of the data source and is a very small price to pay for having interchangeable components. Yes the view could just load the CSV directly and output it, but then its data source must always be a CSV file.

But for a static page, as Lemon described it, there isn’t any data. It’s just a static view. So to me, it seemed… unusual… when you delegated that to the model.