Controller in MVC

You’ll have to ask the framework authors for their reasoning, however, the simple explanation is that they don’t do it because they’re not MVC. They don’t separate the controller from the application state. In 2008 Padraic Brady pointed out that none of the big frameworks have a proper model ( http://blog.astrumfutura.com/2008/12/the-m-in-mvc-why-models-are-misunderstood-and-unappreciated/ ) and nobody took any notice. Which is a shame because it’s a necessary step prior to seeing the benefit in SoC that MVC offers over PAC :slight_smile:

Thanks for that. You (and Padraic) just opened/ changed my perspective on MVC considerably. FSUC. LOL! I also like the idea of just calling the view, for a simple read request and have the view get its data as requested and respond accordingly.

Scott

1 Like

But you haven’t specified where the new View code goes. In past discussions, you often say, “and now you just wire it up,” and your wire-it-up code ends up doing exactly the same thing that the controller used to do (select data from the model, pass it to a view). So you haven’t actually changed how anything works. You only renamed “controller” to “wire-it-up”.

Plus, you overlook a lot of possible complexities. Let’s say, for example, that if the blog is empty, then we want to render a different view?

In this case, you picked a poor example. The Symfony blog code you’re working from was either trying to keep things simple or they made a mistake. In real or serious applications, the data selection code goes in a repository class.

And in your code, the view is hardcoded in your “wire-it-up” code. Again, you didn’t change anything, you just renamed it.

I mentioned this clearly: new View goes at exactly the same point you have new Controller already. In most frameworks there is a router that takes a URL string and routes to a specific controller. Instead of routing to a controller, route to a model, view and controller.

But this is display logic so belongs in the view:

if ($this->model->getBlog() == null) {
 echo 'That blog could not be found';
}
else {
echo $this->model->getBlog()->title;
//...
}

edit: If you really did want another view:

if ($this->model->getBlog() == null) {
$view = new ErrorView('Blog not found!');
echo $view->output();
}
else {
echo $this->model->getBlog()->title;
//...
}

You’d probably want a template system inside the view but as a starting point, “display an error when no blog is found” is… display logic and belongs in the view.

Not really, it’s the difference between

class A {
	private $b;
	
	public function __construct(B $b) {
		$this->b = $b;
	}
}

and

class A {
	private $b;
	
	public function __construct() {
		$this->b = new B;
	}
}

In the $this->render example, the view is hardcoded to the controller, in mine the view is assigned to the route, there’s a difference.

To elaborate using Symfony’s example from here: http://symfony.com/doc/current/book/routing.html

You’d get more flexibility if you actually had a model, and decoupled the controller from the view:

$collection = new RouteCollection();
$collection->add('blog_show', new Route('/blog/{slug}', array(
    '_controller' => 'AppBundle:Blog:show',
    '_model' => 'Model',
    '_view' => 'Twig(show.tpl)',
)));

Because then it lets me do something like:

$collection = new RouteCollection();
$collection->add('blog_show_rss', new Route('/blog/rss/{slug}', array(
    '_controller' => 'AppBundle:Blog:show',
    '_model' => 'Model',
    '_view' => 'Twig(rss.tpl)',
)));

or

$collection = new RouteCollection();
$collection->add('blog_show_json', new Route('/blog/json/{slug}', array(
    '_controller' => 'AppBundle:Blog:show',
    '_model' => 'Model',
    '_view' => 'JsonView',
)));

Which lets you reuse views, controllers and have proper models where you cannot currently.

That’s already easy to do.

$this->render('article/index.'.$format.'.twig'); // where $format can be html or rss or json or whatever

But separating the handling of user input from the view selection doesn’t seem better to me. Often times the view we pick depends on the result of user input.

EDIT: I will say, however, that your last post is – in spirit – what I’m looking for from you. Before we try to come up with a solution, I think it’s important to clearly identify the problem. If you can identify and demonstrate a flexibility problem with web MVC, then we can better discuss the merits of various solutions.

Picking one of various formats, for example, is a problem that actually had a very simple solution, but that doesn’t mean there isn’t a different problem that isn’t so easy to solve.

Take a look at #15. By taking common functionality and abstracting it into a reusable controller that is not tied to a specific data source it means you don’t need to write a new controller for each action, you just fill in the blanks. For example, a very common scenario is loading a single record into a specified view.

Most (all?) frameworks require a lot of repeated controller logic for this: Grab the data from the model, pass it to the view and it’s specific for each entity type. For example, showing information about a user might looks something like this:


class UserController extends Controller {	
	public function viewAction() {
		$user = $this->User->findById($_GET['id']);
		$this->set('user', $user);
		$this->render('viewuser.twig');
	}
}

The problem is, elsewhere in the application you have

class BlogController extends Controller {	
	public function viewAction() {
		$blog = $this->Blog->findById($_GET['id']);
		$this->set('blog', $blog);
		$this->render('viewblog.twig');
	}
}

And even worse, where you want to show a blog summary for the home page, and the full content on it’s own page you get repetition (Usually in a different method in the same class)

class BlogController extends Controller {	
	public function viewSummaryAction() {
		$blog = $this->Blog->findById($_GET['id']);
		$this->set('blog', $blog);
		$this->render('viewblogsummary.twig');
	}

	public function viewFullAction() {
		$blog = $this->Blog->findById($_GET['id']);
		$this->set('blog', $blog);
		$this->render('viewblogsummary.twig');
	}
}

And this is for the trivial exercise of loading a single record into a view. For anything more complex e.g.

  • Pagination
  • Lists
  • Tabulated data
  • Form processing

You end up with long winded, repeated controller code. I demonstrated tabular data in #15, but to continue with the single record example, I could make a simple interface:

interface SingleEntity {
	public function setId($id);
	public function getEntity();
}

Which I can then apply to some data source, in this case some sort of ORM:

class OrmMapperEntity implements SingleEntity {
	private $mapper;
	private $id;

	public function __construct(\Orm\Mapper $mapper) {
		$this->mapper = $mapper;
	}

	public function setId($id) {
		$this->id = $id;
	}

	public function getEntity() {
		return $this->mapper->findById($this->id);
	}
}

I can now make a controller that uses the interface:


class SingleEntityController {
	private $model;

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

	public function viewAction($id) {
		$this->model->setId($id);
	}
}

Then make a view, for example for blogs, again this could be reusable if it took a template file as a constructor argument, but to keep it simple…


class BlogSummaryView {
	private $model;

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

	public function output() {
		$blog = $this->model->getEntity();
		return '<h1>' . $blog->title . '</h1><p>' . $blog->summary . '</p>';
	}
}

and similar views for the other uses. I can then just build my routes without adding specific controller actions for each possible action simply because the models and controllers are reusable. In reality the router would be a bit smarter than this, but in the interests of simplicity:

$route['/blog/summary/*'] = function() {
	$model = new OrmMapperEntity(new Mapper('blog'));
	return [new SingleEntityController($model), new BlogSummaryView($model)];
};


$route['/blog/full/*'] = function() {
	$model = new OrmMapperEntity(new Mapper('blog'));
	return [new SingleEntityController($model), new BlogFullView($model)];
};

$route['/user/view/*'] = function() {
	$model = new OrmMapperEntity(new Mapper('user'));
	return [new SingleEntityController($model), new UserView($model)];
};


$route['/product/view/*'] = function() {
	$model = new OrmMapperEntity(new Mapper('product'));
	return [new SingleEntityController($model), new ProductView($model)];
};

Of course the amount of repeated code required for this task is minimal, if you take this process and apply it to Pagination, Tabular data, lists, etc you get reusability which otherwise is impossible.

Consider the following:

interface Searchable {
	public function filter($value);
}

interface Listable {
	public function getList();
}
class OrmFilterList implements Searchable, Listable {
	private $mapper;
	private $searchField;

	public function __construct(\Orm\Mapper $mapper, $searchField) {
		$this->mapper = $mapper;
		$this->searchField
	}

	public function filter($value) {
		$this->mapper = $this->mapper->filter([$this->searchField, $value]);
	}

        public function getList() { 
            return $this->mapper;
        }
}
class SearchableController { 
	private $model;

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

	public function searchAction($filter) {
		$this->model->filter($filter);
	}
}
class ListView {
	private $listable;

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

	public function output() {
		foreach ($this->model as $key=>$value) {
			//...
		}
	}

}

I can then have a searchable list of products:

$route['/product/search/*'] = function() {
	$model = new OrmFilterList(new Mapper('product'), 'name');
	return [new SearchableController($model), new ProductListView($model)];
};

or users…

$route['/users/search/*'] = function() {
	$model = new OrmFilterList(new Mapper('user'), 'name');
	return [new SearchableController($model), new UserListView($model)];
};

or blogs…

$route['/blogs/search/*'] = function() {
	$model = new OrmFilterList(new Mapper('blog'), 'name');
	return [new SearchableController($model), new BlogListView($model)];
};

or categories…

$route['/categories/search/*'] = function() {
	$model = new OrmFilterList(new Mapper('category'), 'name');
	return [new SearchableController($model), new CategoryListView($model)];
};

or whatever, without neededing to add controllers, models or whatever else specifically for them. The only thing I’ve added is a small amount of logic to the router, which has to exist in some form anyway and as I mentioned in #15 if you wanted to take this a step further and have the model provide a bit of metadata to the view, you could even use the same view for each type, although I’ll admit when I tried this I found it a little too restrictive or complex depending on the implementation.

By loosely coupling the controller, view and model (As they are supposed to be in MVC!) you open yourself up to a whole new level of flexibility that just isn’t possible otherwise.

edit: To elaborate a little, it’s possible then to make a route for a products in a specific category just by changing the search field…

$route['/categories/*'] = function() {
	$model = new OrmFilterList(new Mapper('product'), 'categoryId');       
	return [new SearchableController($model), new CategoryListView($model)];
};

Which using the same code, would trigger searchAction(4) when routed to /categories/4

How about authentication? Where would that fall into place?

Scott

Do you mean logging in directly or preventing access to certain routes unless you’re logged in or the log in process itself?

For the former, I actually have the route also return an access level which is checked by the router. If it’s not allowed, it instead routes to an error/login MVC triad and logs the attempt. That said, I’m not really sure this is the best way to do it or not, although doing it in the router is a bit more secure than trying to do it after the various components have been constructed as the objects are never even instantiated unless the access level check passes.

The problem with this, of course, it is puts some domain logic in the router, although this is done by injecting the behaviour.

That was a long post. You’ll have to be lot more specific. I hope you’re not referring again to the data selection part, which was an improvement only because you picked a poor example to start from.

Except loading a single record is rarely as simple as your example. What if the user/blog doesn’t exist? What if the user doesn’t have permission to view that record? The answer could easily differ for each controller. And even the repository call findById may be different. I might call a different method just for one action to lazy load or pre-load. And the arguments may be different too. One controller might use a slug or the session.

In short, in real life, those controllers are actually quite different.

If you’re truly in a situation where you have load by id, display result, load by id, display result, etc over and over, then it sounds like that application is close to being a CMS, in which case, yes, we make some special architecture to accommodate that situation.

If we need to render the same data with the same template in two or more places, then we can literally render(controller(...)) from inside various templates.

For things that are complex such as pagination, those are indeed factored out into their own library/controller/template to avoid repetition.

I really wish you would take the time to build a real application with something like Symfony, because it seems an awful lot of the time where you claim there’s repetition, there actually isn’t.

Also, to clarify… this is considered too repetitious:

$user = $this->User->findById($_GET['id']);
$this->render('viewuser.twig', ['user', $user]);

$blog = $this->Blog->findById($_GET['id']);
$this->render('viewblog.twig', ['blog', $blog]);

// etc for each route/controller

And your solution is:

$model = new OrmMapperEntity(new Mapper('user'));
return [new SingleEntityController($model), new UserView($model)];

$model = new OrmMapperEntity(new Mapper('blog'));
return [new SingleEntityController($model), new BlogView($model)];

// etc for each rout/wire-it-up

It doesn’t seem like you solved anything.

Apples to apples maybe?

$user = $this->User->findById($_GET['id']);
$this->render('viewuser.twig', ['user', $user]);

and

$collection->add('blog_show', new Route('/blog/{slug}', array(
    '_controller' => 'AppBundle:Blog:show',
)));

vs

$route['/user/view/*'] = function() {
	$model = new OrmMapperEntity(new Mapper('user'));
	return [new SingleEntityController($model), new UserView($model)];
};

So yes, there is less repetition, I only have to change the router configuration rather than the router config + controller action. Not that it matters, as I said, it’s fairly trivial when it comes to this example, once you introduce pagination, lists, forms, etc you end up with significantly more repeated logic.

Let’s get back on topic though, I provided some simplistic examples and introduced them as such and your complaint is they’re not like real life?

Again, these are beyond the scope of my simple example but as I already said this is display logic. Saying “Sorry that blog could not be found” when the model returns null is clearly the job of the view, at which point it can output the relevant error by loading a different template, echoing the error or using whatever output mechanism is.

Considering this discussion is about the underlying architecture (Should the application state be stored in a model or the controller?), saying “Symfony has a bunch of workarounds for the problems introduced by poor SoC” isn’t really relevant. Yes, Symfony does work around the limitations, but I’d rather take a step back and ask “Why should(nt) the controller store the application state?” than discuss specific implementations.

And this shows you missed my point, by substituting the model/controller. The whole point I was trying to make is that using MVC proper, I can have two models, one with findById() and one with find([…]) and I can have two controllers, one that reads from $_GET and one from $_SESSION and use either model with either controller. When you have the model merged with the controller you need 4 different methods to do this.

Because Symfony’s router uses a name of something to execute and your router uses a closure function. That detail doesn’t really affect this discussion.

To be clear, none of these implementations – not even yours – is MVC proper. In MVC proper, models are supposed to be observers. Views are supposed to watch for changes and update themselves. And the input a controller interprets is supposed to come from a view that already exists (a click, a keypress, etc). MVC proper simply isn’t applicable to the web, and forcing a square peg into a round hole isn’t making things better.

Not that I’m PHP coder but that doesn’t look like MVC at all. I guess you can consider ‘launch’ as your controller since it’s dealing w/ models and view but it’s doing a bit more than necessary. If possible, you want to pass the model to view and let it deal w/ templates. If anything, it should not be called as ‘MVC’ framework and more like ‘MVC’ library. Essentially, you’re doing the work of ‘wiring’ all by yourself through ‘coding’ and not through the configuration. MVC configurations looks like below

URL: /createUser
Controller: UserController.createUser
View: UserView

As you can see, it’s driven by the configuration and not through manual coding.

The fact that a message is sent over HTTP has zero effect on the efficacy of the pattern. you can send the message over a function call, low level windows API or a screen touch event. When you click a button on a windows application it fires a WM_COMMAND which then gets picked up by the application that it was triggered in. When you click a link on a web page it sends a HTTP request which then gets picked up by the program that it was sent to. The fact that the windows API is more transparent than HTTP has no bearing on the underlying pattern at all.

None of the early references for MVC reference the observer patter, however it is quite clear that the view reads its own data from the model (http://heim.ifi.uio.no/~trygver/1979/mvc-2/1979-12-MVC.pdf , http://www.itu.dk/courses/VOP/E2005/VOP2005E/8_mvc_krasner_and_pope.pdf ).

In this metaphor, views deal with everything graphical; they request data from their model, and
display the data.

Terminology aside, however, I’m not particularly interested in being true to the definition as there’s no reason to assume the definition is correct or the best solution to the task at hand. What I am more concerned with is: Which offers a better separation of concerns? Which conforms better to best practices? Which is more flexible? Which is easier to test?

Let’s look at a Symfony Controller:

Single Responsibility? Well a Symfony controller has to select a view, process user input, request data from the model and update the model… so no.

Favour Composition over inheritance? In Symfony It’s possible but it’s a bit of extra work, most of the big frameworks force you to inherit from the base controller class

Law of Demeter? Again, it’s possible in Symfony but makes things needlessly difficult, instead most examples dig into a repository/doctrine manager to fetch required entities because they don’t have a proper model layer.

Yeah, I think I’ll stick with a proper model that makes it easy to follow these unless there is a compelling reason not to.

Hey Tom,

Couple questions:

Where are you sending the id of the desired entity to the view to render (e.g. how does the * from your router get into the view)?

$route['/users/search/*'] = function() {
$model = new OrmFilterList(new Mapper('user'), 'name');
return [new SearchableController($model), new UserListView($model)];
};

Where do we listen for actionable events (creating, updating, deleting)?

Just to be clear: This wasn’t really supposed to be a working example example, I was trying to keep it as simplistic as possible to demonstrate the concept. However, as you pointed out in my attempt at simplicity I actually missed the controller action name in this example. To clarify: This is an implementation detail and not anything at all to do with MVC and a real router will need to do quite a lot more than is shown there.

This actually answers your second question as well. In reality I have my router match a function and one or more arguments. A better example is a list of users with methods for Delete/Sort/Search/Set Page as per my tabular data

$route['/user/list/{func}/{arg}'] = function() {
	$model = new OrmMapperEntity(new Mapper('user'));
	return [new TabularDataController($model), new UserView($model)];
};

Which will then call $controller->{func}(...{arg}) although you’re right, this example is rather contrived, if {func} isn’t specified it calls a default indexAction();

If you’re really interested, I posted a basic router implementation here: https://r.je/mvc-php-router-dependency-injection.html which is a few years old but along the lines of what my router is doing at the moment. (Although I didn’t include anything about RESTful requests in that article). I didn’t really want to get into “This is how I do it” and “this is how symfony does it” as it detracts from the topic at hand and gets in the way of any discussion about specific concepts by adding needlessly complex code examples and ends up becoming a comparison of the implementations rather than the underlying concepts.

Thanks again for that Tom. I’ve been thinking about a feature, where an admin just adds a URL to their browser. which is a new page they want to compose (following the URL conventions they had set up for themselves, of course). Since they are an admin, the system wouldn’t send them a 404 page, but rather to a “construction” page, where they can start creating their model (if it didn’t already exist, taken from the given URL) and a view for that model and URL. Base CRUD controllers already exist. If some sort of specialized business logic is needed, during any CRUD activity, it would be added to the model and triggered through any model activity (note, this is where the real programming happens). I’ve been studying Symfony and thinking, how am I going to do this elegantly? It seemed a difficult question to answer. I thought my thinking this way was simply my own lack of competence (which it partially was/ is). I really feel at home with Symfony, because it has that “page controller” semblance, like Padraic points out, but in essence, I now see the “Symfony way” as somewhat debilitating towards my own goals.

Scott

My own way of doing it worked from this principle - the model answers questions and takes orders, but doesn’t initiate conversations. The controller routes to the view the user is working on and attaches the models associated with that route to it. The view composes itself and sends back the response. It can ask questions of the model components it was given (usually table objects) but it doesn’t have arbitrary access to all models in the system.

Model, View, Control, these are areas of code, not classes. All three domains have wide enough responsibilities no class can handle all of them. I believe that’s where a lot of the confusion starts.

Isn’t this a bit inflexible? I only ask because when you’re using domain models as MVC models, this causes a flexibility issue which is worked around by having a proper model or the Controller extracts from model and passes to view approach by most frameworks.

I’m probably misunderstanding as it was only a brief description but if your view is doing this:

foreach ($this->table->find(['name' => $this->name]) as $row) {

}

It then makes it impossible to reuse the view and search by a different field rather than name, for example, I can’t substitute the $this->table->find(['name' => $this->name]) query for $this->table->findAll().