SSbits - Home page
Site by Carbon Crayon
Submit a Post >

Tutorials - Big bits of code to help you do more

Controllers Instead of Pages

SilverStripe is great for working with Pages in the CMS.  But what if you need functionality without the need for content or all the "data fluff," like in Aram's tutorial about user registration and profile editing.  Well, believe it or not, SilverStripe is a pretty solid MVC platform, even if you're working outside of the page data objects.  It took me a while to understand how to implement true-ish MVC in SilverStripe, so I thought I'd take the opportunity to share.

First, let's take a look at what we're aiming for:

  1. A straight MVC architecture, we have no need for $Content blocks, etc.
  2. URL Routing that works without pages existing in the CMS
  3. We'd like to retain the ability to use themes, and ensure they still work

So let's start with our controller, we'll call it AccountController.php, and I'm gonna modularize it, so we'll put it in  a folder called Account Management:

[silverstripe-root]/AccountManagement/code/AccountController.php

<?php

class AccountController extends Page_Controller
{
	static $allowed_actions = array(
		'index',
		'register',
		'editprofile'
	);
	
	public function init() {
		parent::init();
		// Requirements, etc. here
	}
	
	public function index() {
		// Code for the index action here
		return array();
	}
	
	public function register() {
		// Code for the register action here
		return array();
	}
	
	public function editprofile() {
		// Code for the editprofile action here
		return array();
	}
}
?>

Now let's take a look at what this is doing. First, we're extending Page_Controller, so SilverStripe knows how to find our page templates, and nest them as Layouts, and whatnot. You could extend Controller, but what I've found is that Controller can't figure out to nest templates very well. It may be useful if you don't want to have Master templates like Page.ss, but for our example, we want themes to still work, so we'll do it this way.

Now come the class methods and variables. I'm gonna breeze through these in bullet form, if you have questions, post a comment:

  • $allowed_actions - this is where we tell SilverStripe what to allow users to do. These should match your method names
  • init() - this is just like in a normal page, where you can add Requirements:: and what not.
  • index(), register(), and editprofile() - these are page actions. They will be referenced by URL/Controller/Action

So we have our controller written, but it doesn't really do anything, and more importantly, SilverStripe has no idea how to access it via URL. So let's fix that.

There are a couple ways to do this, but my preferred method is with Director::addRules(), which you pass an array of rules to "teach" the Director how to route URLs.

[silverstripe-root]/AccountManagement/_config.php

<?php

Director::addRules(100, array(
	'account' => 'AccountController'
));
?>

That's it, now SilverStripe knows to route urls with /account to the AccountController.

So now you may be asking yourself, as I did, "Now what the heck do I name my templates?"

Well, true, it's a bit different, but it's not that bad. You simply follow the naming convention Controller_Action.ss. Here's what our templates would look like:

[silverstripe-root]/AccountManagement/templates/Layout/AccountController.ss

<h1>This is the Index action</h1>

[silverstripe-root]/AccountManagement/templates/Layout/AccountController_register.ss

<h1>This is the Register action</h1>

[silverstripe-root]/AccountManagement/templates/Layout/AccountController_editprofile.ss

<h1>This is the EditProfile action</h1>

And there you have it. Browse to /account?flush=1 or /account?flush=all to clear the template cache, and you should see your respective actions! You can then work with forms and form actions in the exact same way as you do with standard page controllers! Hope this helps!

Ty Barho avatar

Ty Barho

I like to work in SilverStripe CMS. It's fun!

  • Marcus Dalgren
    25/01/2011 4:55pm (4 years ago)

    I've done this myself but I wrote my own nested controller so I don't have to write a new Director rule for every new controller I create.

    Also Page_Controller (and ContentController) kind of expects there to be a SiteTree object linked to the controller, have you had any issues related to this?

  • Ty Barho
    25/01/2011 5:07pm (4 years ago)

    Marcus,

    I'd love to see the code for that!

    So far I haven't had any issues with there not being a SiteTree object linked to the controller, though I haven't used this method on larger projects yet. I'm doing one now though, so I'll post here if I run into any issues with it.

  • FullWebService
    25/01/2011 5:09pm (4 years ago)

    I use this more and more. Both my code and SiteTrees are much cleaner now. Great post.

  • Terry Apodaca
    26/01/2011 6:40pm (4 years ago)

    This is great! I was wondering if/when someone would write up an article on this. It reminds me a lot like how ASP.NET MVC tries to be a true MVC framework...

    But very nice work Ty!

  • Frank Mullenger
    26/01/2011 9:28pm (4 years ago)

    Nice technique! Thanks for sharing, I can see its going to help me out on a project I'm currently working on

  • dalesaurus
    27/01/2011 3:42pm (4 years ago)

    Great post Ty! I prefer doing things this way to ensure clean URLs on sites. One trick I like is to replicate the hand $URLSegment as a constant.

    class AccountController extends Page_Controller {
    const URLSegment = 'account';

    public function getURLSegment() {
    return self::URLSegment;
    }
    ...
    }

    and
    Director::addRules(100, array(
    AccountController::URLSegment => 'AccountController'
    ));

    This makes setting redirects, and printing links in templates easy:

    Redirects:
    Director::redirect('/'.AccountController::URLSegment);

    Templates (say, the header of /Layout/AccountController.ss)
    Click here to <a href="{$URLSegment}/balance">check your balance</a>


    Most importantly you can change around your controller URL with a few keystrokes.

  • swaiba
    27/01/2011 3:44pm (4 years ago)

    Great article - I was very happy when I learned this!

    2 quick comments...
    1) if you do need to have a page, but still want it hidden from the site tree you can do it like this...
    class MyPage extends Page implements HiddenClass {}

    2) you can configure the director rule in a more complex way to pass IDs etc in...
    Director::addRules(100,array(
    'urlsegment/$Action/$ID/$OtherID' => 'MyPage_Controller',
    );

  • Ty Barho
    27/01/2011 4:11pm (4 years ago)

    Dale,

    Oh yeah, I like that. Never thought about doing URL handling that way. I've seen people use link functions in controllers, like

    public function Link($action = null, $id = null) {
    return Controller::join_links($this->URLSegment, $action, $id);
    }

    So that would definitely be a helpful method for getting that to work in an "MVC Not Page" type setting. Thanks!

  • moloko_man
    02/02/2011 11:52pm (4 years ago)

    Coming from using CakePHP a lot, this is what I like to see. Great for cleaning up (getting rid) of those pages you don't need in the CMS, and very straight forward.

    I'm mid project for a big work project and this will come in very handy now.
    Thanks.

  • Martijn van Nieuwenhoven
    12/02/2011 2:37pm (4 years ago)

    My favorite usecase for this, is to create a Search results page, so searchresults will has its own page instead of displaying them on the current page the search is perfomed:

    http://www.sspaste.com/paste/show/4c84cb3f4d2e1

    In this way you can also create nice SEO pages on keywords by redirecting to site.com/jobsorproductsorwhatever/{$keyword}

  • MisterAC
    21/02/2011 1:25am (4 years ago)

    I, like others, like the idea of cleaning up the tree by keeping code in controllers alone. However, I've been trying to figure out how to add these controllers to the main site navigation (generated with Menu(1), Menu(2) and so on in the templates).

    Have I just missed something obvious? Perhaps somebody more experienced could point me in the right direction.

  • Adi Surya
    22/02/2011 2:56am (4 years ago)

    Thx, very helping for my next project.

  • Ty Barho
    22/02/2011 3:20am (4 years ago)

    MisterAC,

    Take a look at the class ContentController (I did a Google search for "docs for class ContentController"). That should help you move forward. I'll look into doing another post on getting controllers into the Site Tree

  • Darren-Lee
    09/04/2011 11:01am (4 years ago)

    Nice post, Ty! I'll try this technique next time...would have been handy for a recent recruitment project I was working on.

    Also @Martijn van Nieuwenhoven - many thanks for sharing your code snippet on SSPaste.com - I can see it being very useful for me in the future.

  • Manuel
    15/05/2011 8:23am (4 years ago)

    @Ty

    I'm at the same point as MisterAC.
    Don't know how to add the Controller to the Menu. Even after looking at the ContentController docs and code.

    Would be awesome if you could do a tutorial on that or at least explain it in a comment.

    Thanks,
    Manuel

  • alexyoungs
    26/09/2011 10:41am (3 years ago)

    Hi all,

    I'm having a bit of trouble with the whole pages vs controllers technique.

    What I'm trying to achieve is a front end CRUD system, giving certain users the ability to add/edit/delete records without using the CMS module.

    The problem is that whenever I construct a $Form in one of my 'pages', the URL that the form is posted to (so the URL that is automatically added to the action attribute of the form) is always set to '/ControllerName/MethodName/', rather than the URL alias that I set with Director::addRules in my config.

    Subsequently the form always fails and I'm not sure how to fix the problem. Has anyone come across this issue before? Will $url_handlers in my controller help in any way?

  • alexyoungs
    27/09/2011 10:35am (3 years ago)

    Actually, I've now realised that there was another error causing the form submit to fail.

    To be honest I didn't actually realise that the form first of all posts data back to it's own method and THEN passes data onto the method set with FormAction.

    The combination of these two things had me all confuzled. Sorted now!

  • neros3
    01/07/2012 5:17pm (2 years ago)

    Hey - I'm having a problems with getting to render the correct view.
    If I just do as the above in the action (return array();) - then nothing is shown at all.
    The only way I can get an output is by either
    1. return $this; (but this will not render the right template XX_myaction.ss)
    2. return $this->customise($data)->renderWith(array('XX_myaction', 'Page'));

    I'm runining version 3.0.

    I would love if I shouldn't call all the customise and renderWith and specify the exact template all the time.

  • Mountain Tiger
    20/11/2012 2:02pm (2 years ago)

    i want to pass data array from index method to view files how?

Post a comment ...

You cannot post comments until you have logged in. Login Here.

Advertisement

Site of the Month

Find SSbits on

Top Contributers

Rank Avatar Name
1 article image Aram Balakjian
2 article image Daniel Hensby
3 article image Marcus Dalgren
4 article image Hamish Campbell
5 article image Ty Barho
6 article image Martijn van Nieuwenhoven
7 article image Darren-Lee
8 article image Roman Schmid
9 article image Matt Clegg
10 article image dalesaurus

View full leaderboard


Advertisement