Garden Preview Part IV: Views

Garden Preview Part I: Overview
Garden Preview Part II: Filesystem
Garden Preview Part III: Anatomy of a Request
Garden Preview Part IV: Views
Garden Preview Part V: Data
Garden Preview Part VI: Models & Forms
Garden Preview Part VII: Ajax
Garden Preview Part VIII: Plugins
Garden Preview Part IX: Roles & Permissions
Garden Preview Part X: User Registration
Garden Preview Part XI: Structure

Last week I described how requests to Garden are handled by the dispatcher. This week I’m going to explain how those requests are rendered by the requested controller.

Once a controller method is called to handle the request, how is the xhtml of the page put together? Views.

There are two types of views in Garden: “Views” and “Master Views”. A view relates directly to the controller method that called it and handles rendering content related to that request. You can typically think of a view as the content for that page. For example, if a Vanilla->Discussion->All() method is called, the view for that method would handle rendering all of the discussions. Everything that is rendered around the discussions is handled by the Master View.

The Master View allows you to create a consistent layout for the pages in the application. A single master view defines the look and feel and standard behavior for all of the pages (or a group of pages) in the application.

Let’s go back to the filesystem so you can get a better picture. In the following example, the request in the url would have been the following:

http://myserver.com/garden/profile/index/mark

So, this means that Garden’s “Profile” controller was requested, the “index” method was requested from the profile controller, and the first argument into the index method is “mark”. In other words, the request was: $ProfileController->Index('mark');

Let’s take a look at the profile controller’s index method:

public function Index($UserReference = '') {
   $UserModel = new UserModel();
   $this->User = $UserModel->GetByReference($UserReference);
   $this->Render();
}

Simply put, the index method grabs some data that can be later used in the profile controller’s “index” view, and then calls the Render() method. The Render method is defined on the base Controller class; the class from which all controllers are extended. The Render method performs the following tasks:

1. Finds and fetches the view.
2. Finds and fetches the css for the view.
3. Finds and renders the master view.

In the most simple example, the view will be as it appears in the image above: in a “profile” folder within the “views” folder, and named after the method that was called: /views/profile/index.php.

By default, the controller will fetch that view from the context of the FetchView method of the base controller class. In plain-English: the view file is included within a method on the Controller class called “FetchView”. That is why I placed the user data within a $this->User property in my $ProfileController->Index() method, above; so it could be accessible from within my view file. The view file likely will contain something as simple as this:

<h3>Basic Information</h3>
<dl class="Info">
   <dt>Joined</dt>
   <dd><?php echo $this->User->DateFirstVisit; ?></dd>
   <dt>Visits</dt>
   <dd><?php echo $this->User->CountVisits; ?></dd>
   <dt>Last Active</dt>
   <dd><?php echo $this->User->DateLastActive; ?></dd>
</dl>

It is important to note that I have not used a custom templating engine like smarty. This is because PHP already is a templating engine. Further to that point, I am only resorting to switching to PHP when echoing data to the screen, and I am not building large concatenated echo statements. All of these efforts are taken in order to make the theme author’s job easier.

Once the view is fetched, it is added to an asset collection. By default it is added to the “Content” asset collection. The rest of the page (the “Frame” of the page) is handled by the master view. By default, the “default.master” master view is used unless another master view is specified. In the image above you can see that there are a few different master views available in the Garden application: default, email, error, and setup. The css files for each of those master views are named accordingly, as well. So, the default.master view has a default.screen.css file, the error.master view has an error.screen.css file, etc. Again, in the most simple of examples, the master view is located in the “view” folder of the application, and the related css files are located in the “design” folder of that application.

Here is what the default.master view looks like:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-ca">
<head>
	<?php $this->RenderAsset('Head'); ?>
</head>
<body<?php echo Attribute(array('id' => $BodyIdentifier, 'class' => $this->CssClass)); ?>>
   <div id="Frame">
      <div id="Title"><a href="<?php Link('garden/user/browse'); ?>"><span><?php echo $this->Head->TitlePrefix; ?></span></a></div>
      <?php $this->RenderAsset('Menu'); ?>
      <div id="Content"><?php $this->RenderAsset('Content'); ?></div>
      <div id="Foot"><?php $this->RenderAsset('Foot'); ?></div>
   </div>
</body>
</html>

As you can see, the master view handles the basic structure of a page. As I mentioned above, by default the view that is fetched by the controller is added to an asset collection called “Content”, and that asset collection is rendered in this master view when the $this->RenderAsset('Content') method is called – right there in the middle of the master view.

As you can see in the master view above, there are a number of different asset collections available for application and plugin authors (Head, Menu, Content, and Foot). An application author has the ability to add as many of these as he/she desires and add to them as necessary. The idea behind these asset collections is that while the basic request can be handled and placed into the “Content” asset (ie. the profile information being displayed from the example above), there are still a lot of other elements a person may want on a page. Plugin authors may want to add assets to the head, menu, or foot. They may even want to add assets to the content collection. Garden itself has other UI components that get rendered in the head and menu asset collections (which will be discussed in a later preview).

So, in the most basic example, the “index” view is requested from it’s controller’s view folder, the default.screen.css file is requested from the application’s design folder, and the master view is requested from the root of the application’s “view” folder. What other ways could the views and css have been retrieved?

The controller uses Garden’s FileSystem object to search the application for the appropriate file to handle the request. Whatever the filesystem object finds first, it will use.

Views are retrieved with the following precedence:

1. An explicitly defined path to a view.
ie. a plugin’s custom view location
2. Application-specific theme view.
ie. /path/to/garden/themes/theme_name/app_name/views/controller_name/view.php
3. Garden-wide theme view.
ie. /path/to/garden/themes/theme_name/views/controller_name/view.php
4. Application default.
ie. /path/to/garden/applications/app_name/views/controller_name/view.php

Master views are retrieved with the following precedence:

1. Application-specific theme view.
ie. /path/to/garden/themes/theme_name/app_name/views/default.master
2. Garden-wide theme view.
ie. /path/to/garden/themes/theme_name/views/default.master
3. Application default.
ie. /path/to/garden/applications/app_name/views/default.master
4. Garden default.
ie. /path/to/garden/applications/garden/views/default.master

CSS files are retrieved with the following precedence:

1. Application-specific css.
ie. /path/to/garden/themes/theme_name/app_name/design/default.screen.css
2. Garden-wide theme view.
ie. /path/to/garden/themes/theme_name/design/default.screen.css
3. Application default.
ie. /path/to/garden/applications/app_name/design/default.screen.css
4. Garden default.
ie. /path/to/garden/applications/garden/design/default.screen.css

You might be wondering things like: can I change the view or master view that handles my request on the fly? Of course you can! Everything in Garden was written so that you have total control over what comes out on the other end. In my example above, I could change my views on the fly with something like this:

public function Index($UserReference = '') {
   $UserModel = new UserModel();
   $this->User = $UserModel->GetByReference($UserReference);
   $this->View = "somethingelse"; // Use /views/profile/somethingelse.php to handle the content
   // ... or ...
   $this->View = "/some/other/view.php"; // Use some custom view to handle the content
   $this->MasterView = "setup"; // Use the setup master view to render my contents
   $this->Render();
}

Views are a lot to take in, and they certainly might be daunting upon first read. But I can guarantee that I have found them to be utterly simple and flexible to use. Think of the possibilities you can accomplish with views! You could create a master view that handles rendering of RSS feeds. You could port your request method to use your RSS master and render all of your contents with a custom RSS view. You could grab the email master view and send out your content to some email address(es) before rendering. You could bypass the rendering altogether and deliver json data. The possibilities are endless, and that is the real power of views.

29 Responses to “Garden Preview Part IV: Views”

  1. MySchizoBuddy

    it can be further simplified, inside the controller
    $Head = $this->RenderAsset(‘Head’);
    then inside the view

    Why?
    because the designers won’t know about the RenderAsset method and what argument to pass it to get the header tag. do i have to pass the argument ‘header’ or ‘head’, which one is it?

    Also in top of each view a commented list of variables that can be used in that view
    /*
    $Head = Header
    $Content = Main Content
    $RsideBar = Right side bar
    $Foot = Footer
    */
    So now the designers know about which variables it has at its disposable, what are they called and what they mean

    this should make views drop dead easy for designers

  2. MySchizoBuddy

    hmm it removed my php code
    here is it again inside view

  3. Mark

    Comments are, of course, welcome in the view files. I left stuff like that out for the brevity of the article.

    The reason that the head, sidebar, footer, etc aren’t handled inside the view will be revealed later when I talk about ajax.

  4. MySchizoBuddy

    errrrrr
    mark you need the preview comment button
    so inside view you will just echo $Head, rather than $this->RenderAsset(‘Head’)

  5. Mark

    Yeah, that’s fine. It’s just assigning the value to a variable before echoing. A little extra processing and nicer on the eyes.

  6. Jeff

    It looks like we need to call $this->Render(); for each of the methods inside of the controller to render it to the browser?

    So, if we have a private method inside of the controller that only the controller should be using, then what will happen if the user browses to that private controller in the browser?

    Do we have to specify that we want an error view to display for private methods? Sorry if my wording is off, it is somewhat hard to try to explain.

  7. Mark

    @Jeff – The dispatcher is what calls the controller method, so if you try to access a private (or protected) method on the controller through the url, it will result in a controller-not-found error.

    For example, if you tried to access this url:

    /application/mycontroller/youcantseeme

    and the controller was created like this:

    class MyController extends Controller {
    private function YouCantSeeMe() { DoSomethingAwesome(); }
    }

    … the dispatcher would essentially be doing this:

    $MyController->YouCantSeeme();

    Which, as you know, will not work. So, the dispatcher will throw a “controller not found” error when in debug mode, and a custom 404 error when not in debug mode.

    If, however, you had a utility method that was public for some reason, you would be able to access it through the url, but unless you render something, the screen would just be blank.

  8. Adrian

    Interesting.

    Makes perfect sense to me why you’d use the master view… It means that you can replace the content area of the page via ajax right?

  9. Mark

    @Adrian – right you are.

  10. MySchizoBuddy

    i’m going to talk about testing right now. Have been reading Google Testing Blog and finding out how crappy my code really is. I came across law of demeter. “Don’t look for things, ask for them”
    this code posted above isn’t easily testable.

    public function Index($UserReference = ”) {
    $UserModel = new UserModel();
    $this->User = $UserModel->GetByReference($UserReference);
    $this->Render();
    }

    lets go in to the details.
    First, you don’t need the UserModel object, what you need is whats inside the UserModel object. so what you say, well now i cannot mock the UserModel object cause its initialized inside the method. Everytime i test the Index methond it goes and instantiates the UserModel. So now if my test fails i don’t know if the fault is my Index method or the usermodel, or the dependency of the usermodel.

    lets change the variable names a bit and see whats going on
    Class Car {
    $Engine

    public function Car($ModelNo = ”) {
    $EngineFactory = new EngineFactory();
    $this->Engine = $EngineFactory->GetModelNo($ModelNo);
    }
    }

    Now you see the problem. Why does the Car know about the factory and why is it going to the factory to get the engine that it will save in itself.
    Now if you have to use the Car somewhere else, you cannot use it without taking the factory with you. if i pass in a fake modelNo, the factory might fail and hence the car might fail.
    Car needs an engine and it should ask for the engine not look for it in the factory. essentially for a good testable code, “Don’t look for things, ask for them”

    There is a highly recommended video presentation on Google testing blog about this very specific code, its conclusion is that 99% of the time you shouldn’t use the “new” statement. new statement inside the method is a pain for one test, but new inside the constructor is a pain for tons of tests. you shouldn’t use .getX either cause then your looking for things inside something else.
    http://googletesting.blogspot.com/2008/11/clean-code-talks-dependency-injection.html

    Code reviewers guide has an article about this as well, with code examples and fixes, the code is in java though. easy and simple explanation
    http://misko.hevery.com/code-reviewers-guide/flaw-digging-into-collaborators/

    I don’t want to be a obnoxious, Garden is almost finished, so lets complete it and not worry about testing for now. you have come too far to refactor your code now.

    We should talk about testing for the future versions of garden. everyone wins if the code is easily testable. so and steady towards fully easy testable code. the core garden team should have a dedicated testing person for future versions.

    Thanks for all the work with garden.

  11. Mark

    I agree with a lot of what Misko says in that talk.

    Now, in the example you criticized above, the requested method has absolutely no way to take the actual “User” object as a parameter. Remember, the method is taking a pointer to a user object because there is no way you could pass an entire object in a querystring (and still have it readable/user-friendly).

    Further to that point, if I were to abstract that out so that the dispatcher operates as a factory and creates objects to pass into controller methods, the querystrings would get way more convoluted as more and more information about the type of objects being requested is added.

    Now, if we’re talking about objects in Garden like a Database object, or a Session object, or a FileSystem object, or an Email object, or a Form object, or a Validation object, or an Authenticator object: THAT is really where Misko’s talk applies and dependency injection makes a lot of sense. To quote him:

    “For the most part, your services, your objects that are responsible for talking to each other and collaborating: those need to ask for their dependencies.”

    In that sense, in it’s current form, the controller object is a form of factory in that it instantiates objects that it needs, passes other objects into those objects, etc.

    I’d be interested to hear how you would suggest changing the dispatcher & controller relationship so that they could still handle requests in the way they currently do, but also follow the principles of DI.

  12. Mark

    Also – as a point of interest – I just ran a search through the code of Garden, and I found that about 80% of all “new” references were made in controllers. I did some more digging into where the other instances were, and I found that I could easily pull those out and place them in the controller. This would essentially make all of the classes in Garden *besides* controllers very easily testable.

    I might just do that…

  13. Mark

    Here’s another question, MySchizoBuddy: what do you think of Singletons?

  14. y2kbg

    “Makes perfect sense to me why you’d use the master view… It means that you can replace the content area of the page via ajax right?

    @Adrian – right you are.”

    thats awesome, it will be sweet

  15. Mark

    @y2kbg – It is a lot of fun when I start to turn on the ajax stuff in Garden.

    I should mention that I’ve made Garden so that it works with javascript turned off, too. I’ll talk about all of that when I get to the Ajax preview…

  16. MySchizoBuddy

    singletons are evil for testing

  17. Adrian

    Any particular reason? I rather have one instance of something going wrong than multiple ones! :)

  18. MySchizoBuddy

    @Adrian.
    http://blogs.msdn.com/scottdensmore/archive/2004/05/25/140827.aspx

  19. MySchizoBuddy

    Global State and Singletons
    http://www.youtube.com/watch?v=-FRm3VPhseI

  20. Dinoboff

    Controllers take input, validate them, get data from models and return a view. Do they need unit testing for them? Can it be left left to functional tests?

  21. Mark O’Sullivan - Blog » Blog Archive » Garden Preview Part V: Data

    [...] Click here to read the Garden Preview Part I Click here to read the Garden Preview Part II Click here to read the Garden Preview Part III Click here to read the Garden Preview Part IV [...]

  22. Brad Touesnard

    I first heard of applications sharing a single copy of a framework from the CodeIngniter docs, but I really like how you’ve made applications front and center and wrapping user privileges system around them. It’s almost like applications are plugins to the framework. A novel concept, good work.

    Is there a reason why the master view files don’t have “.php” extensions? AFAICT, they have PHP code in them, which will be readable when the file is accessed directly.

    Looking forward to the release.

  23. Mark

    “It’s almost like applications are plugins to the framework.”

    Now that you mention it, that’s exactly what it is like. I always envisioned an application being like a big plugin with lots of features.

    Re: .master extensions. I only did it so that there was a visual cue that the file was a master view. It is parsed by the framework as a PHP file, regardless of the extension.

  24. Adrian

    Re: .master extensions. I only did it so that there was a visual cue that the file was a master view. It is parsed by the framework as a PHP file, regardless of the extension.

    I think what Brad means is that if something has a non php extension, it will be loaded as plain text if you type the http address of it, thus exposing the php.

  25. Mark

    Yeah – I’m aware of that. But the master view *shouldn’t* contain any sensitive information. And I could always add a .htaccess file to rectify that, right?

  26. Brad Touesnard

    This is a similar security measure as adding index.html files in all subdirectories (which almost all frameworks do). Sure the web server should be configured to disallow directory listings, but why not add index.html for those who overlook this.

    Besides, I doubt there’s a reason to omit the .php extension besides personal preference.

  27. Mark O’Sullivan - Blog » Blog Archive » Garden Preview Part VI: Models & Forms

    [...] Part I Click here to read the Garden Preview Part II Click here to read the Garden Preview Part III Click here to read the Garden Preview Part IV Click here to read the Garden Preview Part [...]

  28. Mark O’Sullivan - Blog » Blog Archive » Garden Preview Part IX: Roles & Permissions

    [...] Part I Click here to read the Garden Preview Part II Click here to read the Garden Preview Part III Click here to read the Garden Preview Part IV Click here to read the Garden Preview Part V Click here to read the Garden Preview Part VI Click [...]

  29. Mark O’Sullivan - Blog » Blog Archive » Garden Preview Part VIII: Plugins

    [...] Part I: Overview Garden Preview Part II: Filesystem Garden Preview Part III: Anatomy of a Request Garden Preview Part IV: Views Garden Preview Part V: Data Garden Preview Part VI: Models & Forms Garden Preview Part VII: [...]

Leave a Reply