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

Tutorials - Big bits of code to help you do more

Using a nested controller for generic URL handling

 Source files (2.2 KB)

In this tutorial we'll be taking a look at SilverStripes nested controller concept and how you can use it in your own projects. Keep in mind though that this tutorial is probably for the more advanced users and for those who whish to learn abit more about how SilverStripe works. I will be referencing Controllers Instead of Pages by Ty Barho and the nested controller we'll be building will be for handling his scenario.

I have also taken the liberty of including a zip file which contains all the code examples from Tys tutorial + the new controller and the new URL rule for it.

The nested controller

So what is the nested controller? A nested controller is a controller whose main task is to pick another controller which will handle the actual request. If we take a look in Sapphires _config.php (sapphire/_config.php) file we find this on line 34 or thereabouts.

 

Director::addRules(1, array(
	'$URLSegment//$Action/$ID/$OtherID' => 'ModelAsController',
));

When someone is visiting your site this is the rule that is invoked pretty much every time. The only exception is when someone is visiting the root of the site or if some other special actions is being invoked like visiting the admin or dev/build. The first argument in addRules sets the priority to 1 which is the lowest priority possible. This means that it will be easy for us to hook in our own nested controller before ModelAsController.

ModelAsController and NestedController

Before we make our own let's have a look at ModelAsController. The class can be found in sapphire/core/control/ModelAsController.php. The class declaration looks like this

class ModelAsController extends Controller implements NestedController {
.
.
.
}

As you can see ModelAsController implements an interface called NestedController (sapphire/core/control/NestedController.php) which looks like this

interface NestedController {
	public function getNestedController();
}

An object interface is a way to define methods that a class has to implement. If you implement NestedController on one of your classes that class has to contain the method getNestedController, otherwise your code will throw an error. An interface only contains method declarations, it doesn't actually have the code or implementations for those methods, that's left to the implementing class. You can read more about interfaces in the PHP manual here.

The interface only defines one method so getNestedController() is the only method that actually has to be in an implementation of NestedController. We will be inheriting ModelAsController so all we need to do is replace the logic for picking the controller with our own version.

The logic you decide to use to pick a controller is totally up to you so I will simply be showing a simple example that fits the basic mysite.com/account => AccountController with very little frills. If you need more advanced logic for your implementation then by all means go ahead and use that instead. This tutorial is just for showing you where to put it and giving you an example of how to write it.

The code

I have decided to call this class BaseController and the code for this class is as follows

 

class BaseController extends ModelAsController {

	public function getNestedController() {
		$request = $this->request;

		$URLSegment = $request->param('URLSegment');
		$query = new SQLQuery();
		$query->select("URLSegment")->from("SiteTree")->where("URLSegment = '".$URLSegment."' AND ParentID = 0");
		$result = $query->execute();
		if ($result->numRecords() > 0) {
			return parent::getNestedController();
		}

		$controllerClass = ucfirst($URLSegment)."Controller";

		if (class_exists($controllerClass)) {
			$result = new $controllerClass();
			if (is_subclass_of($result, "Page_Controller")) {
				return $result;
			}
			else {
				return parent::getNestedController();
			}
		}
		else {
			return parent::getNestedController();
		}
	}
}

So what does the code do? It starts out by fetching the URL segment from the current request. After that an SQL query is created which checks the database for any pages on the root level of the site which matches the URL segment. The scenario here is that a content manager for the site has gone and created a page with the same URL as one of your controllers. In this case the created page gets precedence if one is found but you can of course decide to go the other route and just remove that part or comment it out.

After that we simply upper case the URL segment and add "Controller" to it. Then we check if this class actually exists. If it does we instance it and then check if it's a subclass of Page_Controller. This is mostly to fit into Tys scenario where sub classes of Page_Controller are used as controllers. If you're sub classing something else then check for that instead. If all is well the instanced controller is returned, otherwise we hand off to ModelAsController and it's business as usual.

One Director rule to rule them all

The last part we need to make this work is to create an URL rule for the BaseController. Luckily we have the perfect rule for the job already. Add this to your _config.php.

Director::addRules(2, array(
	'$URLSegment//$Action/$ID/$OtherID' => 'BaseController',
));

Astute readers will notice that this URL rule looks alot like the one used for ModelAsController. The only differences are that we've upped priority to 2 and we're pointing it to the BaseController instead of ModelAsController.  This means that our new rule will be tested before the original and our BaseController will be called instead.

With this in place you no longer need to add a rule to the director for every new controller you create as long as you stick to the same formula for the controller name.

Marcus Dalgren avatar

Marcus Dalgren

Marcus is a web developer currently working at a small startup company called Overlay in Gothenburg, Sweden. He also does freelance work for small to medium sized businesses in the Gothenburg area.

  • Bart van Irsel
    09/02/2011 3:56pm (4 years ago)

    Thanks for this nice tutorial Marcus.
    I will definitely use this in future projects.

  • tbilyn
    03/06/2011 1:24am (3 years ago)

    Thanks a lot.
    I was looking for the way how to access dataobjects by the first parameter after base url. This method is exactly what i need.

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