MVC, HTML, and JSON - what goes where?

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
inghamn
Forum Contributor
Posts: 174
Joined: Mon Apr 16, 2007 10:33 am
Location: Bloomington, IN, USA

MVC, HTML, and JSON - what goes where?

Post by inghamn »

So we've been rockin' and rollin' with an MVC design for our content manager. Now, my usability person wants a few drop downs to be repopulated on the fly. Hey, no problem - I've done quite a few bad AJAX implementations. Now to do a good one.

In this case, we want to start with a small list of users in a <select> and repopulate it with the full list if the user desires. I've even got a users controller that current displays the list of users in HTML, using a template for the site and all that.

My first stab at this involves passing format=JSON in the URL. And doing a REST style service. Where I start getting concerned is that, as time goes on, and we want to support JSON, XML-SOAP, XML-RPC, etc, that's a whole lot of stuff going on inside switch{} statements. Is there a better way to organize all this? Having 90% of the code inside of a switch make me feel like I should split it out into seperate controllers.

Here's my first attempt at adding JSON to the controller.

Code: Select all

$userList = new UserList();
$userList->find();

$format = isset($_GET['format']) ? $_GET['format'] : '';
switch ($format)
{
	case 'json':
		$template = new Template('JSON');
		$template->blocks[] = new Block('users/userListJSON.inc',array('userList'=>$userList));
	break;

	default:
		$template = new Template();
		$template->blocks[] = new Block('users/userList.inc',array('userList'=>$userList));
}

$template->render();
User avatar
inghamn
Forum Contributor
Posts: 174
Joined: Mon Apr 16, 2007 10:33 am
Location: Bloomington, IN, USA

Post by inghamn »

I guess it boils down to two options:

1. Front Controller checks format and loads different controllers for different output formats.

or

2. Every controller checks format and loads appropriate views.


Are there other options out there?
What solution makes the most sense?
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I think I would make JSON, XML-SOAP, XML-RPC calls each be a method in the related Action Controller. I am assuming that they all share one or more common models, so they would be different Views run by the same Controller.
(#10850)
User avatar
inghamn
Forum Contributor
Posts: 174
Joined: Mon Apr 16, 2007 10:33 am
Location: Bloomington, IN, USA

Post by inghamn »

Fair enough. That's pretty much the route I'm taking right now. The code I posted is essentially my Users Action Controller

I'm just about to launch into adding RSS support across the content manager. I'm looking down that path and seeing myself typing the same stupid format code into all my controllers and was hoping for a better way.

Kinda thinking out load here...but...what if I put View stuff into a directory structure that included the format? ie..

/blocks
/html
/json
/rss
/xml

Then, the controllers don't need to parse format, they just pass it onto the Template, and let the Template load the appropriate files?

In this case Template is serving as a Composite View. Does it make more sense to put HTML, JSON, RSS, etc logic into the View? Or should that logic really stay in the controller?
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

inghamn wrote:Fair enough. That's pretty much the route I'm taking right now. The code I posted is essentially my Users Action Controller
I was thinking that the Front Controller would select the output type:

Code: Select all

class MyController {
     function __construct() {
          $this->userList = new UserList();
          $this->userList->find();
     }

     function jsonAction () {
                $template = new Template('JSON');
                $template->blocks[] = new Block('users/userListJSON.inc',array('userList'=>$this->userList));
                $template->render();
     }

      function defaultAction() {
                $template = new Template();
                $template->blocks[] = new Block('users/userList.inc',array('userList'=>$this->userList));
                $template->render(); 
     }
}
Obviously you want to move the duplication in the Actions into the View.
inghamn wrote:I'm just about to launch into adding RSS support across the content manager. I'm looking down that path and seeing myself typing the same stupid format code into all my controllers and was hoping for a better way.

Kinda thinking out load here...but...what if I put View stuff into a directory structure that included the format? ie..

/blocks
/html
/json
/rss
/xml

Then, the controllers don't need to parse format, they just pass it onto the Template, and let the Template load the appropriate files?

In this case Template is serving as a Composite View. Does it make more sense to put HTML, JSON, RSS, etc logic into the View? Or should that logic really stay in the controller?
This is a really good question and there seem to be many answers. I think go with the file structure that seems to make sense to you -- it will probably evolve as you get into it anyway. I do think Composite View makes sense where the View just picks the right template, if you can get away with that.
(#10850)
User avatar
inghamn
Forum Contributor
Posts: 174
Joined: Mon Apr 16, 2007 10:33 am
Location: Bloomington, IN, USA

Thanks

Post by inghamn »

Thanks, arborint. That all makes sense.

I'm launching into building RSS support across the whole application today. I can let you know how it goes. Even, though I'm not using the Zend Framework for MVC, if folks are interested, I can post code for the solution I end up with.
User avatar
kyberfabrikken
Forum Commoner
Posts: 84
Joined: Tue Jul 20, 2004 10:27 am

Post by kyberfabrikken »

FWIW, I usually implement handlers for supporting fileformats, such as RSS and JSON, as separate methods on the same controller, which would handle the normal (text/html) type request.

Instead of requesting the content-format as a query string parameter, you might want to use content-negotiation, which is the way, HTTP was intended to do this. Basically, your ajax-request should send an 'Accept' header, with the content-type that it expects, and in your controller you can then detect this, and dispatch to the appropriate handler accordingly.
User avatar
inghamn
Forum Contributor
Posts: 174
Joined: Mon Apr 16, 2007 10:33 am
Location: Bloomington, IN, USA

Post by inghamn »

Instead of requesting the content-format as a query string parameter, you might want to use content-negotiation, which is the way, HTTP was intended to do this. Basically, your ajax-request should send an 'Accept' header, with the content-type that it expects, and in your controller you can then detect this, and dispatch to the appropriate handler accordingly.
For JSON, in the case of AJAX, I think I could work that out. In the case of RSS, I'm not sure how I'd implement it. People "subscribe" to RSS, essentially, by bookmarking a URL. Woulnd't I have to come up with some URL that told the person's browser to add RSS to their 'Accept' header?

Having the output format as part of the URL makes it easy for users to get the page in different ways.
http://mysite/news
http://mysite/news?format=RSS
http://mysite/news?format=XML

or in the case of events...
http://mysite/calendar
http://mysite/calendar?format=XML
http://mysite/calendar?format=ICAL

Once I start down this road for output formats, JSON becomes just another output format.
User avatar
kyberfabrikken
Forum Commoner
Posts: 84
Joined: Tue Jul 20, 2004 10:27 am

Post by kyberfabrikken »

inghamn wrote:In the case of RSS, I'm not sure how I'd implement it. People "subscribe" to RSS, essentially, by bookmarking a URL. Woulnd't I have to come up with some URL that told the person's browser to add RSS to their 'Accept' header?
Yes, content-negotiation isn't used a whole lot. Personally, I'm not entirely sure, what I think about the feature. I mostly mentioned it, in case you hadn't thought about that option. Coming to think about it, an RSS feed should probably have its own URL, and not just be identified by a different Accept header.
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Post by Kieran Huggins »

I feel like the site views should be completely separated for each output type, perhaps maintaining a separate directory of templates for each one?
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Kieran Huggins wrote:I feel like the site views should be completely separated for each output type, perhaps maintaining a separate directory of templates for each one?
I think it really depends on the implementation. In many cases the same Model and View can generate the separate output just using different templates. Obviously that does not work with all types. In this way I think the argument for a render tree that can be built of templates sub-Views starts to make sense. Then you can use templates for the easy stuff and sub-Views for more complex feeds.
(#10850)
User avatar
inghamn
Forum Contributor
Posts: 174
Joined: Mon Apr 16, 2007 10:33 am
Location: Bloomington, IN, USA

Post by inghamn »

Well, I've implemented a composite view system. Still not using the Zend Framework, though - it's routing system makes my head hurt :cry:

What I realized with the format issue (at least with my stuff) is that the HTML format was the only format that could have multple possible templates being asked for. In a controller, you might ask for the two-column, html template or the three-column, html template. But all the other formats would really only have one template. JSON, iCAL, RSS - there really isn't alot of different layouts you would want to use for those.

Cacth me, if I'm missing anything there.

Although the View code in my project supports passing Model data to the Views (templates, and blocks) I've left that code out in these examples. Trying to keep it clear on the formatting problem.

Code: Select all

abstract class View
{
	abstract public function render();
}

class Template extends View
{
	private $filename;
	public $outputFormat = 'html';
	private $blocks = array();

	public function __construct($filename="default",$outputFormat='html')
	{
		$this->filename = $filename;
		$this->outputFormat = $outputFormat;
	}

	# Template files must include a call to $this->includeBlocks(),
	# when they're ready for the main content
	public function render()
	{
		include APPLICATION_HOME."/templates/{$this->filename}.inc";
	}

	# Callback function for template files
	private function includeBlocks()
	{
		foreach($this->blocks as $block) { $block->render($this->outputFormat); }
	}
}

class Block extends View
{
	private $filename;

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

	public function render($outputFormat='html')
	{
		include APPLICATION_HOME."/blocks/$outputFormat/{$this->filename}";
	}
}
After updating the Views to handle different outputs, I still wasn't able to comletely remove Format checking from inside a controller. (Front Controller or Action Controller or whatever controller routing you've got.)

I was able to boil it down to just an Either-OR check. Either there was a format requested, or we're using the default template, which would be defined in the controller. In the example below, I leave the default file and format defined in the Template class. So my controller code now looks like:

Code: Select all

$userList = new UserList();
$userList->find();

$template = isset($_GET['format']) ? new Template($_GET['format'],$_GET['format']) : new Template();
$template->blocks[] = new Block('users/userList.inc',array('userList'=>$userList));

$template->render();
This works so far. However, I'm concerned about the need to pass the $outputFormat as a parameter to the block->render(). Waiting until render time to throw an Exception if there's no matching file *might* cause problems later on.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

First, I think it should be:

Code: Select all

abstract class Template 
{
        abstract public function render();
}

class View extends Template 
{
You also might want to think of making a Template class that does templating, and a View class that contains support to composites Templates.
(#10850)
User avatar
kyberfabrikken
Forum Commoner
Posts: 84
Joined: Tue Jul 20, 2004 10:27 am

Post by kyberfabrikken »

arborint wrote:First, I think it should be:
I would prefer:

Code: Select all

interface View
{
  public function render();
}

class Template implements View
{
...
}
A template is not the only technique, for rendering output (Depending of course on, what you put in the name 'Template').
User avatar
inghamn
Forum Contributor
Posts: 174
Joined: Mon Apr 16, 2007 10:33 am
Location: Bloomington, IN, USA

Post by inghamn »

arborint wrote: You also might want to think of making a Template class that does templating, and a View class that contains support to composites Templates.
I'm definitely keeping my options (and hopefully mind) open. I think we're all talking to the same idea. The only issue is semantics - which seems good. :D

In an attempt to be clear...in the work I've done thus far.
View = output base class
Template = Boilerplate content
Block = chunks of content composited inside the Template.


Sadly, if there's already been confusion on words I've chosen, then maybe it's time I renamed sfuff. 'Block' has always bothered me anyway, I just couldn't come up with a better word. 'Subview' maybe? Subtemplate? The mind boggles. 8O
Post Reply