Jump to content

An almost MVC approach to using templates


larrybotha

Recommended Posts

Hey guys, 

I'm new here, and I'm loving ProcessWire and the community spirit so far.

We're just about to start using ProcessWire for our new clients requiring PHP solutions. 

Moving in a Rails direction, structure in our projects is becoming more and more of a necessity. As such, I've tried to employ some MVCish techniques in creating a boilerplate for ProcessWire for getting projects going quickly.

One of the primary goals of this structure is to make it easier to separate logic from markup, and to prevent tags from being split over templates. Keeping `body`, `html`, and structural markup open and close tags in the same file greatly reduces cognitive overhead, and reduces opportunities for mismatching tags to exist. This approach also reduces duplication, and is great for keeping files small, focused, and organised.

The main structure of the boilerplate is well defined, and ready to use in production.

The repo is available here:

https://github.com/fixate/pw-mvc-boilerplate (link updated 2014/01/17)

Structure

Although not completely MVC (using classes for controllers feels redundant, there are no models, and a full MVC approach will require a fair amount of customisation), it is heavily MVC inspired.

Additionally, the structure is an extension of Soma's delegate approach.

File structure:

├── site
  ├── assets
  ├── modules
  .
  .
  .
  ├── templates     // boilerplate contents here
    ├── assets      // css, js, fonts, images, etc.
    ├── controllers // variables and functions specific to templates
    ├── errors
    ├── partials    // markup not specific to any particular template
    ├── views       // layouts specific to a template
    ├── _init.php   // used to load global and template-specific controllers
    ├── main.php    // the 'alternate template' for all templates
  .
  .
  .

**NB: This structure has largely been updated and improved - see the UPDATE - 2014/01/17 at the bottom of this post!

Controllers

Controllers hold template specific variables and functions. There is also a global controller responsible for making global fields, such as SEO fields, available everywhere, as well as being responsible for the actual delegation. 

Template logic should be handled as much as possible from within controllers.

Views

Views are responsible for handling markup and output. Views have available to them both the global controller, and their own controller. Logic should be, as much as makes sense, handled in a controller, with the view pulling the results in for display.

Partials

Partials hold markup not specific to any particular template on its own, such as the `<head>`, header, navigation, scripts, or footer. As with views, it is best to keep these as logicless as possible.

_init.php

_init.php is responsible for making controllers available to views. Controllers are only included if they exist - sometimes a template-specific controller may not be necessary, in which case you won't need to create one.

main.php

main.php is the default layout (equivalent to layout/application.html.erb in Rails) into which everything is rendered. This file has been kept small deliberately to let partials and views manage more fine grained markup structures, while this file serves the main site structure. A call to render_view(), defined in global-controller.php, is responsible for delegating rendering to the view of the current template.

Additionally, there is a constant defined in globals-controller.php useful for serving different assets depending on if you are working in a local environment, or if your site is live. This is useful for preventing Google Analytics from running in a dev environment, or for using unminified scripts for debugging.

This boilerplate eliminates the need to do much configuration when beginning a project, apart from having to change each template's alternate template in the admin.

I hope this will assist in quickly organising and developing new projects!

------------------------------

UPDATE - 2014/01/17:

------------------------------

  1. All files for rendering are postfixed with .html.php in good ol' Rails fashion.
  2. main.php has been removed in favour of mvc.php. mvc.php requires config/boot.php which then handles which controllers, views, etc. are used
  3. the main layout is now found in views/layouts/application.html.php - like Rails again.
  4. partials are now kept inside views/
  5. each page template can have its own optional controller, or simply inherit functionality only from ApplicationController
├── site
  ├── assets
  ├── modules
  .
  .
  .
  ├── templates       // boilerplate contents here
    ├── assets        // css, js, fonts, images, etc.

    ├── controllers   // variables and functions specific to templates
    ├── core          // core mvc files - base controllers etc. (project specific stuff does not go here)
    ├── errors
 
    ├── views         // folder for template files, layout files, and partials
      ├── layouts     // application layout
      ├── partials    // markup not specific to any particular template
    ├── mvc.php       // the 'alternate template' for all templates
  .
  .
  .
  • Like 17
Link to comment
Share on other sites

Greetings Larry,

Excellent discussion!  Coming from the world of PHP frameworks, I am also very curious about the most "MVCish" way of working in ProcessWire.

An interesting challenge is that a lot of the "M" and "C" necessities in frameworks are handled differently (I would say better) in ProcessWire.  And yet, as we develop more complex sites, the templates can get quite involved with material that properly should be abstracted into some sort of classes/functions/controllers.

What's great about ProcessWire is that we have the best aspects of frameworks, with the freedom to be creative in how we structure views and functions/classes, and it takes care of many crucial database elements in efficient ways.  We then have the potential to develop interesting structures, without being forced to worry about too many conventions.

I'd love to see a discussion here where we share creative ideas for structuring ProcessWire sites in MVC ways.

Thanks,

Matthew

  • Like 4
Link to comment
Share on other sites

No problem, landitus! 

Matthew, I'd love to know others thoughts on an MVCish approach. I'm new to the concept, and am learning about MVC primarily through Rails, so I've got a lot to learn. A friend in our office is more of the MVC guru, so a little of this has been guided though his expertise.

I had great success moving some features of the search template into the controller last night - I'm really happy with the outcome (although there's room for a little polish). I'm excited to get working on abstracting the sitemap to its controller, and then eventually adding a basic contact page in the same way, but I'm confident that this approach will assist in keeping my templates organised and maintainable.

One thing that I can see being useful is if there are shared helpers - files which contain shared logic, say for forms, that are specific to multiple templates, but not appropriate for the global controller. I'm not sure how these can be implemented in a nice generic manner, but I'll be giving it some thought.

  • Like 1
Link to comment
Share on other sites

You might want to have a look at this module of my coworker which does exactly what you've mentioned: It separates the "view" (mostly logicless) templates from something called "data providers" (controllers) and adds some logic to define reusable chunks. Plus: It also plays nice with this one which lets you use twig for the view templates.

  • Like 2
Link to comment
Share on other sites

  • 2 months later...

I definitely don't understand all things going on in this boilerplate and i won't be using it anytime soon but this will be great for the more experienced/advanced php guys.

Maybe you should add the minimum required PHP version in the README.

Link to comment
Share on other sites

Good point SiNNuT.

We'll have to test the minimum PHP version, as we're using traits in there. We're using PHP 5.4, so 5.3 will have to be tested against.

There's still a fair amount of work to do in terms of the docs, but we'll get there eventually. I'm actually a total PHP noob - most of the magic in there is not mine! Reading through how things are being called, required, and included should shed some light on what's going on - we've deliberately tried to keep files as light as possible.

Link to comment
Share on other sites

I find the topic interesting and having got in contact with Rails recently the approach seems very nice. 

One thing I have not been able to do is the usage of other layout besides the application.html.php that is defined by default. I can't get the override_view_name and page_specific to be called.  

It would be nice to have a more complete example and for instance one that would make usage of different layout, as in the real world :-) ´

Keep up the great work!

AM

Link to comment
Share on other sites

@xweb - Hi, I mainly wrote the PW MVC framework. Thanks for pointing that out - this is why we need to get some tests up! The project which I built this for didn't need to change the layout, so it didn't get manually tested - I'll improve/fix the layout API today and write some tests in the coming weeks. (I'm aware that is the wrong order to do things :P)

As for the the methods used as an example - override_view_name and page_specific, those should work if you have a page called 'override-view-name' that uses the 'Home' template (i.e. HomeController).

To be clear, the override_view_name and page_specific are NOT special functions, but just examples (quite confusing examples I'm just realising). If you have a page called 'foo-bar' or 'foo_bar' using the 'Baz' template then you can optionally put a function called foo_bar in your BazController which will be called for that page.

Link to comment
Share on other sites

  • 4 weeks later...

@stanimal - I gave the names override_view_name as the example in your documentation but I was trying a simple example with a basic-page template and page named Contactos. I suppose the function below contactos should we called?

class BasicPageController extends ApplicationController {
    function index() {
        return $this->render(
             array(
                'extraScripts' => array('themes/js/jquery-1.10.1.js','bootstrap/js/bootstrap.js','themes/js/jquery.flexslider.js','js/main.js')
             )
        );
    }
    
    function contactos() {
        return $this->render('')->set_layout('contactos');
    }
 
}
 
Somehow I've been unable to do this until now.
 
Thanks
 
AM
Link to comment
Share on other sites

@stanimal - Ok, found it. Either you change the documentation or the implementation ;-)

If you have a page called 'foo-bar' or 'foo_bar' using the 'Baz' template then you can optionally put a function called page_foo_bar in your BazController which will be called for that page. The part of page_ is on line 49 of the Controller.php

    function call() {

        $func = 'page_'.f8\Strings::snake_case($this->page->name);    
 
Works beatifull!
 
AM
  • Like 1
Link to comment
Share on other sites

  • 1 month later...

Hello all,

What would be the correct way to share a variable between different partials and views? 

I have a View where I add a Javascript object but only in the scripts.html.php I can instantiate it (after scripts are inserted in the html file).

Obviously I don't want to use a global variable :-)

Thanks!

 

Link to comment
Share on other sites

hey xweb,

Sorry about only replying now!

You can add vars to views either on a per view basis, or to all views, by doing the following:

Make a variable available everywhere:

...
// controllers/application_controller.php

class ApplicationController extends Controller {
  ...

  function initialize() {
    ...
    
    // look in core/controller.php to see how add_view_vars() works
    $this->add_view_vars('my_var', 'my var value');

    ...
  }
}
// basic_page.html.php
<?= $my_var ?>

Make a variable available to a particular view:

...
// controllers/basic_page_controller.php

class BasicPageController extends Controller {
  ...

  function index() {
    /**
     * use the implicit template name (basic_page in this case), 
     * and make $my_var available to the view
     *
     * look in core/controller.php to see how render() works
     */
    return $this->render(null, array('my_var' => 'my var value'));
  }
}
// basic_page.html.php
<?= $my_var ?>

Make a variable available to a partial:

Variables can be passed through to partials in a similar fashion to how the render() function is used within controllers.

// basic_page.html.php
<?= $this->partial('my_partial', array('my_var_in_partial' => $my_var) ?>
// my_partial.html.php
<?= $my_var_in_partial ?>
Link to comment
Share on other sites

@OrganizedFellow MVC's not too bad, really. I was always intimidated by it, but all it really is is this:

  • Models -> object definitions, and communication with the database (not really applicable in PW because the API speaks with the DB).
    Models are an interface between the database, and controllers
  • Views -> Logicless (mostly) templates which are responsible for markup, and display of data.
    Views communicate only with controllers
  • Controllers -> functional and logical aspects regarding instances of an object (in our case, instances of page templates).
    Controllers are an interface between Models, and Views

As a side note, we have a yeoman generator which will quickly help getting an entire PW project set up: https://github.com/fixate/generator-fixate-pw

I'll eventually cover this in a separate post.

  • Like 3
Link to comment
Share on other sites

This looks very cool and well thought out, but I'm wondering... Is there an advantage to having your controller code in a separate file rather than just putting it at the top of your template? Since the code is applicable to just one template, I don't see the advantage of putting it in a separate file.

This whole MVC inside of MVC is kind of confusing me. Doesn't PW also already provide you with a way of adding new controller logic through creating modules?

Link to comment
Share on other sites

The advantage of having it in a separate file is that you are separating your concerns. Logic is kept with logic, markup is dedicated to displaying and layout of the data. This makes it easy to understand where things are, how they got to be there, and where to add what as you extend a site. Have a read through just the views in the boilerplate - there is no repetition, everything is well organised and brief, and it is easy to understand where markup is being obtained.

Many functions will be specific to just one template, but other logic is likely to be shared across a number of templates. Abstracting logic away from markup ensures that you can quickly find where to add logic, or where logic is being defined. You can see how we have achieved this with the controllers/traits files - SEO, Search, Opengraph, etc.

A nice result of this methodology is that your files are often shorter, and easier to manage.

In the Ruby realm it's considered good practise to keep functions down to approximately 5 lines, so that they are brief, and achieve only one goal. Keeping files to an approximate maximum length of 100 lines is another good practise - try reading through WordPress core files; it won't be long before you find a monster 1000+ line file (possibly even a single function of that length). That's insane, a nightmare to navigate and manage, and is plain horrible for devs when debugging WordPress' innumerable quirks. When your files and functions start exceeding these numbers, there's often room to neaten things up, make things more concise, and abstract functionality elsewhere. It helps you, helps future devs, and makes unicorns poop rainbows!

You can, of course, write all of your logic into the template, but then may you lose reusability of the code, and will undoubtedly lose brevity, or have to add logic in places which may not make sense. MVC removes this issue entirely - you always know what goes where. ProcessWire allows you to use _init.php and other mechanisms to handle this, but that can quickly become unwieldy, so abstracting concepts to separate files can then become beneficial.

I'm sure the boilerplate can also be achieved using modules, but it made sense for us to keep the controllers right with the views (as in Rails), since controllers are created as we add templates in the admin. Perhaps in the future it would be beneficial to move the logic out to modules, but we'll only look at that should we see a benefit over how we're doing it right now. It's awesome that we have the flexibility to make such decisions with ProcessWire.

Another cool thing is that when you don't want to use the MVC approach, such as when you want a template to redirect - then you don't use MVC! Point the template directly to its file, and put the redirect straight in there, bypassing all the boilerplate logic. You get the best of both worlds.

  • Like 5
Link to comment
Share on other sites

  • 1 month later...

As a side note, we have a yeoman generator which will quickly help getting an entire PW project set up: https://github.com/fixate/generator-fixate-pw

I'll eventually cover this in a separate post.

Hello Larry,

this is great. Thank you for putting the Yeoman genarator together. Would you mind elaborating a little more on this, maybe in a new Yeoman generator thread?

I think this can drastically improve our workflow with PW.

Cheers

Gerhard

Link to comment
Share on other sites

Hi @gebeer

Ye, I must outline some of the features of the generator - it can definitely improve workflow!

It's a little opinionated - it generates projects using our SCSS framework, our KSS Styleguide Boilerplate, an extensive Gruntfile for much awesome automation, and of course the MVC Boilerplate, all of which are fundamental to our ProcessWire projects.

As a quick overview, you can expect the following structure from a newly generated project :

├── database              // location for automated db dumps
├── src                   // PW install location
├── styleguide            // KSS Boilerplate, and SCSS (style.css built to templates)
├── .bowerrc              // tell Bower where to install libraries
├── .editorconfig         // normalise line endings, tabs, etc for all team members
├── .gitattributes        // have git normalise line endings cross platforms
├── .gitignore            // default ignores
├── Gruntfile.coffee      // automated deliciousness
├── package.json          // Grunt dependencies
├── private.json          // .gitignored - put sensitive data here (SSH keys, db creds etc)
└── private-sample.json   // a template private.json for other devs to add non-committed sensitive data

I'd like to put together a post covering some finer details in the next 2 weeks or so - things are a bit crazy here at the moment!

I'll update here to let everyone know!

  • Like 2
Link to comment
Share on other sites

Hi Larry,

thanks for the quick reply.

I just test installed it on my dev box and am browsing through the structure. Indeed, this is pretty opinionated. So I'm not sure if I would want to work with it (no offense intended).

It would be great if we could pull together a more general yeoman generator. I#ll start a new thread on that so we can get some feedback from the community.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...