Gallery2:MVC Structure - Gallery Codex
Personal tools

Gallery2:MVC Structure

From Gallery Codex

Introduction

Gallery uses a Model/View/Controller paradigm for its execution flow. While many off-the-shelf php frameworks (such as Zend Framework, CodeIgniter) exist, Gallery's is custom-written. This page gives a medium-level introduction to that framework at a level intended to be of interest to Gallery users and useful to module authors and would-be Gallery core hackers. Familiarity with the HTTP's POST and GET features will be assumed. At times reference will be made to the Gallery code which is always the best detailed reference for Gallery operation. Line numbers refer to G2.3RC1, the latest release at the time of writing.

For new Gallery users, one of the difficulties in appreciating the operation of Gallery's MVC system is the URL Rewrite module which creates human-friendly urls in the browser's address bar. Unfortunately in doing so it obfuscates the view or controller information which would otherwise be there. So if you're interested in following any of the examples here you may find it easier to disable the URL Rewrite module first.

The MVC model described

The theory behind the Model-View-Controller paradigm belongs in Computer Science, and the web is full of information about the theory for those who care to look. In practice MVC describes a division of labour between different portions of code. The Model represents the data, in Gallery's case directories full of pictures and a database of information about them. The Controller is the code that alters the state of the data, by sending new entries to the database or copying files, thereby adding or deleting pictures for example. Finally, the View is the code that represents and renders the state of the data to the user: a page of photographs, or a page with a form in which to enter new photograph names or locations to upload.

In Gallery terms, the Model remains a theoretical construct of databases and files, but Views and Controllers are the lifeblood of any Gallery module and collections of Views and Controllers are the php code that make up the core of any module. Gallery has a GalleryView class, and a GalleryController class which are the base classes from which Views and Controllers are extended. And both classes have well-defined APIs which describe how they interact with the rest of the Gallery code.

How do Views and Controllers fit into Gallery's execution flow?

Gallery code execution is controlled by main.php, the script that the webserver first turns to when loading a gallery page. Inside the header information of the browser's page request is extra information that Gallery uses to determine which View or Controller to execute for that page request. The particular View or Controller is specified by the g2_view or g2_controller variables included in the header or body of the request (for GETs and POSTs respectively.) Gallery doesn't distinguish between variables from GET requests and POST requests, so a g2_view or g2_controller can be specified by either method.

As an aside you should know that the g2_ prefix for variables included in the page request is stripped off by Gallery when the information is parsed. For instance, look at main.php line 169:

  
list ($controllerName, $viewName) = GalleryUtilities::getRequestVariables('controller', 'view');

The code collects either a Controller or View name from the request variables, specifying them not as 'g2_controller' and 'g2_view' without the g2_ prefices as 'controller' and 'view'.

So the first View or Controller to be executed is the one that's specified in the browser's request. But that's not necessarily the end of the story. Remember a Controller alters the Model but doesn't present anything to the user. A View can format data to show, but isn't permitted by MVC convention to alter the Model. And a typical page request involves first changing some data then displaying something like the results of the data that was changed. So Views and Controllers have to run in sequence to achieve the right result.

Gallery execution flows downhill from Controller to View - the Controller changing data as required, then afterwards the View prepares the page to be shown (with the assistance of templates which we'll cover another time) giving the webserver something to return to the user's browser. The user's original page request will therefore include (amongst other information) the name of the Controller needed to execute the desired change in data. That controller will run - and then hand over execution to a view to display the results to the user's browser. And that's where things stop, until the next page request. It's the mechanism for this handing over that we'll look at in detail.

Of course as a kind of special case the user's request may not change any data - perhaps it's just a request to display something, in which case the original request contains not a Controller name but a View name instead. And that View gets executed directly. In strict terms of numbers the majority of requests to Gallery are just View requests - to display a photo or an album, typically, so they mainly specify a g2_view variable in the request information.

It's also possible - if not good programming practice - for a Controller to hand over to another Controller - and that one even to a third - before executing a View. But when a View is reached it's the end of the line. The View causes html to be written back to the webserver which returns it to the browser.

Gallery also has a (configurable) default View which is loaded if no View or Controller is specified explicitly in the request. See main.php at 321:

    
if (empty($viewName)) {
    $viewName = GALLERY_DEFAULT_VIEW;
    GalleryUtilities::putRequestVariable('view', $viewName);
}

GALLERY_DEFAULT_VIEW is defined in the file modules/core/classes/GalleryConstants.class, line 230:

define('GALLERY_DEFAULT_VIEW', 'core.ShowItem');

- so if you don't specify a View explicitly, core.ShowItem is the one that's loaded.

Let's examine a few example Gallery urls before moving on. Here are some typical page requests (remember, with URL Rewrite disabled.)

http://localhost/main.php?g2_view=tags.TagsItemEdit&g2_itemId=7

Here the view to be loaded is the tags.TagsItemEdit, and the other variable (g2_itemId) is used to indicate which item is having its tags edited.

http://localhost/main.php?g2_view=core.SiteAdmin

This is a request to display the main site admin page.

http://localhost/main.php?g2_view=core.ShowItem&g2_itemId=7
That one's a request simply to display the main album (the item with id 7 is usually the main album in a Gallery installation) although you're more likely to see it as
http://localhost/main.php?g2_itemId=7
bearing in mind the default view, or, even just
http://localhost/main.php
as the main album is also the default item!
http://localhost/main.php?g2_controller=core.Logout

That one runs the logout controller - if you use it when you're logged in, by the time it's finished you won't be.

Views and Controllers have a specific naming convention which allows the Gallery core to load the correct php file given only the View (or Controller) name. The requirement is to specify the name as the full pair of

modulename.ViewOrControllerName

There's more to say on Gallery directory structure another time - but be aware that given the module name and the View/Controller name the filename and location is determined for you.

Most of the direct links in Gallery are links to Views - because Controllers by and large need extra information to function, such as the directory to import photographs from, or a new album title. The most convenient way to provide this data to gallery is in an html form where the Controller name is harder to dig out. But the principle is the same. Buried in each html form that submits to gallery is a Controller name, and inside that Controller is code to read the rest of the data in the form and act appropriately. When it (and any subsequent Controllers) have run they hand over to a View which formats data to show you in your browser.

Moving from Controller to Controller to Controller ... to View

To understand how Controllers hand off to Views (or to each other) we need to look at the Gallery API for Controllers. Gallery declares a GalleryController class (for which the code is in modules/core/classes/GalleryController.class - again, for the ultimate authority on the Gallery code, you have to see the Gallery code itself.) All Controllers within gallery have to extend class GalleryController and therefore implement a function handleRequest(). The controller's function handlerequest is called from main.php line 248:

/* Let the controller handle the input */
list ($ret, $results) = $controller->handleRequest($form);

Two things to note: firstly the $form variable as an argument to handleRequest(). We'll look in detail at Gallery's handling of forms another time, but it's no leap of faith to see that if a controller requires the information from an html form it will find it in the $form variable that the Gallery framework supplies. Secondly, it is information in the $results variable returned by the Controller's handleRequest() function that the code in main.php uses to direct execution to the next Controller or View.

The details of $results are so important they're documented at considerable length in the docblock comment in the GalleryController.class file You can access the comments at the Gallery apidoc site, http://galleryproject.org/apidoc/GalleryCore/Classes/GalleryController.html#methodhandleRequest, and it may be helpful to do so while reading the following section.

Essentially a Controller has choices about how to send execution onward. To move to a View it can either redirect or delegate. To move to another Controller it must carry out a redirect. A redirection means that main.php will build a new url based on the results (possibly with a new g2_controller variable in it) and redirect the user's browser, causing it to reload that new url. You can see that in main.php, in function _GalleryMain_doRedirect() from 607. Line 674 is key:

GalleryUtilities::setResponseHeader("Location: $redirectUrl");

A redirection causes a whole new browser request and a whole new page load. That means a tearing down of the Gallery data structures and an exit from php (just like the end of any page load) then when your browser comes back with the redirected request Gallery is initialised again, main.php starts again at the top and execution goes to the redirected controller (or view.)

By contrast, a delegation is a continuation of the same php process. After returning execution to main.php (and after discerning that a delegation has been selected) main.php determines the name of the View to show: (it must be a View, main.php line 305)

	    /* Save the view name, put the rest into the request so the view can get it */
	    foreach ($results['delegate'] as $key => $value) {
		switch($key) {
		case 'view':
		    $viewName = $value;
		    break;

and then drops through to execute it, just as if that view had been specified in the html request in the first place.

To indicate whether to redirect or delegate a Controller sets the values in the $results array it returns to main.php. To redirect, it sets

$results['redirect']['view'] = 'next.ViewName';

or

$results['redirect']['controller'] = 'next.ControllerName';

or to delegate:

$results['delegate']['view'] = 'next.ViewName';

The choice of page-reload (for a redirect) vs. no-page-reload (for a delegation) makes some significant differences to the server and to the browser. At the server end, because a redirect is a completely new page all the php variables including the ones that the Controller had access to and may have set have long since been wiped by the time the next Controller or View is executed. This can make it awkward for the next Controller or View in line as there may be information that needs to be transmitted, such as the results of the changes wrought by previous Controller. To get around this problem the Gallery framework provides handling for a special variable, returned as the array $results['status'] (a sub-array of $results) by the outgoing Controller. Gallery's main.php recovers this under the name $status and stores it in the Session store (main.php line 283). Gallery's implementation of session handling is a database table whose entries are keyed by the user's cookie. There's much more to be said about Gallery Sessions, but here the Session is used to make the $status variable persistent from a Controller to an immediate downstream View after a redirect - $status is restored from the Session as part of the initialisation of the View.

Aside from the $results['status'] / $status special variable, there are two other options for a Controller to pass information downstream. It can do so as key/value pairs in the redirection request, in which case when the redirect url is constructed the key names have the g2_ prefixed and the information appears in the url as regular request variables. Alternatively the Controller can store anything it likes in the database; this has the widest scope - after all it's mainly what Controllers are for - but isn't necessarily appropriate for some of what needs to pass from a controller to a following View.

When handing over to a View, a Controller has the alternative method - to delegate. After delegation the View is loaded as part of the same page load, so it has access to the same global data structures as the delegating controller. (It also has specific access to the same $status variable contents just like it would have after a redirect, since $status gets put into the Session then recovered again as part of the same request.) Additionally, when requesting a delegation a Controller has another option for passing on information to the View: the $results['error'] variable. If the outgoing Controller sets array elements in $error - for example like this:

$results['error'][] = 'tooManyCooks';
$results['error'][] = 'brothSpoiled';

- then main.php will set two new request variables, tooManyCooks and brothSpoiled, to have the value '1' so that the View in receipt of execution can detect them just as if they'd been set by the requesting browser as g2_tooManyCooks=1&g2_brothSpoiled=1 in the header (main.php lines 299-301). By a judicious choice of the names that the Controller put into the elements of $results['error'] it can insert variables directly into the $form array too. We'll look at this when discussing Gallery's form handling. This feature of using $results['error'] is only available for delegation; anything returned in $results['error'] is ignored on redirection.

One element that is common to both redirections and delegations is that the outbound Controller can set additional request variables. These are available after a delegation because main.php detects and inserts them one by one with calls to

GalleryUtilities::putRequestVariable($key, $value);
.

They're also available after a redirection because they're inserted as key/value pairs (with the g2_ prefix) into the url that makes up the redirection request. The Controller or View that is delegated or redirected to can't distinguish how the request variables were set when it recovers them. The syntax for setting extra request variables is - unsurprisingly - identical to setting the name of the next View or Controller, since View and Controller names are set in urls as request variables. For instance:

$results['redirect']['myVariable'] = '27';

will result, after a redirect, in a request variable of g2_myVariable being present in the url, set to a value of 27. After the following line is executed and a delegation occurs the reader is invited to guess at the results!

$results['delegate']['myVariable'] = '27';

At the browser end, there's also a visible and real difference between delegation and redirection. After a delegation because it's part of the same request the contents of the address bar don't change and an F5-type page reload will cause Gallery to re-run the upstream Controller. That's because if the Controller was originally reached via a link or GET-type request the url will contain a g2_controller=... entry, and if the Controller was reached via a POST request (form submission) the form will be resubmitted (after a browser warning about resubmitting a form.) On the other hand after a redirect the browser address bar will update to show the new View's address (with a g2_view=... entry) an a page refresh will cause Gallery to rerun not the upstream controller but the view only.

Should I stay or Should I go? To Redirect or Delegate?

After some fairly complex discussion of the differences between the options of redirection and delegation, it turns out to be relatively simple in practice to choose beween the two. If a Controller completes its data changes successfully it should redirect. Most often that redirection is to a View, with the Controller setting relevant elements in the $results['status'] element to allow the View (or in practice the View's template) to indicate the success to the user. If the Controller was unable to achieve its aims for changing the database it should set appropriate elements of $results['error'] and delegate.

Much of the time a Controller has a single associated View, and that View (via a template) loads a form. The form fields are passed to the Controller on submission and the Controller parses them and modifies the database. If the parsing of the form fields fails or the database modification can't be carried out for some reason the Controller should set an entry in $results['error'] and delegate back to the same View that loaded the form that submitted to that Controller. The View or its template is then able to display the error in an appropriate place along with an appropriate message (eg. the email address was invalid next to the "Email Address" field in a form). A page refresh by the user will re-submit to the Controller with new form data if any of the fields were modified, so the Controller can have another go at carrying out the database changes. Because the database wasn't affected on the first submission it's likely to be safe to resubmit to try the action again.

If however the Controller successfully completed doing whatever it needed to do to the database then it will set an appropriate entry in $results['status'] and redirect - and for most Controllers it will redirect to the same View. The View will use the $status fields to show a message (Your changes have been saved for instance) and because the redirect has changed the address in the browser address bar to the View (...g2_view=...) a browser refresh will not attempt to modify the database a second time by executing the Controller again but instead show the View a second time. This is most likely to be the correct (desired) behaviour.

How To Return (or There and Back Again)

Before we wrap up this discussion of Views and Controllers, a Controller has one more special return value that we should mention. It's an alternative form of redirection and it's useful when a Controller expects to be inserted in the middle of a workflow. It also requires the cooperation of the upstream link (or form) that launches the browser to the 'inserted' controller. Let's use the typical example of a login controller to illustrate how it works.

Whenever a page includes a link to login like on Gallery's front page, you'll see the expected request variable for the controller (...g2_view=core.UserAdmin&...) but you'll also see a g2_return variable which contains the (relative) url of the current page. In this case something like ...&g2_return=/main.php&...

Following that link executes the login controller as expected. The Controller uses a third option for exiting, aside from explicitly setting a Controller or View name to redirect or delegate to. By setting $results['return'] = 1 it causes main.php (line 270)

	/* Try to return if the controller instructs it */
	if (!empty($results['return'])) {
	    $redirectUrl = GalleryUtilities::getRequestVariables('return');
to perform a redirect to whatever has been set as the return url - not by the Controller - but the page from which the login Controller was launched. Mostly that will mean returning to that same page - so with the cooperation of the sending page a controller can very easily return to the page from which is was launched. In most cases when a module implements a Controller the same module (and therefore the same module author) decides how and where to place, and importantly with which request variables to accompany the link or form that runs that controller. So it's not a problem to make sure the g2_return value is correctly set.

Of course if the login fails or for some other reason depending on the Controller it always has the option to redirect or delegate to a specific, named View or Controller as usual.

What if a module doesn't redirect or delegate, or return?

That's an easy one. Main.php requires the existence of $results['error'], $results['status'] and at least one of $results['delegate'], $results['redirect'] or $results['return']. Miss any of those requirements and there's a special Gallery error waiting to be yours at main.php line 257:

	
/* Check to make sure we got back everything we want */
	if (!isset($results['status'])
		|| !isset($results['error'])
		|| (!isset($results['redirect'])
		    && !isset($results['delegate'])
		    && !isset($results['return']))) {
	    return array(GalleryCoreApi::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__,
		'Controller results are missing status, error, (redirect, delegate, return)'),
		null);
	}

Views can Redirect too

Although we haven't looked at the structure of Views very much in this page, it's worth noting for completeness that Views also have an opportunity to redirect the browser. In particular if you examine main.php at line 466 you'll see the following:

	    list ($ret, $results, $theme) = $view->doLoadTemplate($template);
	    .
            .
            .
	    if (isset($results['redirect']) || isset($results['redirectUrl'])) {
		if (isset($results['redirectUrl'])) {
		    $redirectUrl = $results['redirectUrl'];
		} else {
		    $redirectUrl = $urlGenerator->generateUrl($results['redirect'],
							      array('forceFullUrl' => true));
		}

		return _GalleryMain_doRedirect($redirectUrl, $template);
             }

The function in GalleryView.class doLoadTemplate(...) makes a call to the particular View's loadTemplate(...) function. That returns a $results[] array similar to what a Controller returns from its handleRequest(...) function, although the required contents are slightly different. The View returns must return either a $results['body'] containing as a string the path and name of a template file (.tpl) to display (we'll cover Templates as they're used in Gallery another time), or alternatively, a entire URL in $results['redirectUrl'] or the piecewise components (just like a Controller would) in $results['redirect'].

That just about wraps it up?

Pretty much. Following this explanation will give you a good insight into how the execution path trundles around when you load a gallery page, and will be most useful for writing your own modules if that's your goal. Thanks for following.

Alecmyers