Skip to content

Kirby 3.9.8

Shared Controllers

A simple example

We'll use the <title> and <meta name="description"> tags as an example. The easiest way to add those two tags to our site would be to add them directly into the header.php snippet. Something like this:

/site/snippets/header.php
<!DOCTYPE html>
<html>
<head>

  <!-- The title tag shows the title of our site and the title of the current page -->
  <title><?= $site->title() ?> | <?= $page->title() ?></title>
  
  <!-- The meta description shows an excerpt of the main text -->
  <meta name="description" content="<?= $page->text()->excerpt(120) ?>">
</head>

That's a perfectly fine and reasonable solution. But what if we want to show a slightly different title for the homepage for example?

/site/snippets/header.php
<!DOCTYPE html>
<html>
<head>

  <!-- The title tag shows the title of our site and the title of the current page -->
  <title><?php e($page->isHomePage() , $site->title() . "|" . $site->tagline() , $site->title() . "|" . $page->title()) ?></title>
  
  <!-- The meta description shows an excerpt of the main text -->
  <meta name="description" content="<?= $page->text()->excerpt(120) ?>">  

</head>

This is already starting to look ugly and things will only get messier if we start adding more logic or other tags. It then makes sense to move the logic from the snippet into a controller.

Let's do that.

Setting up the default controller

The first thing we need to do is set up our default controller. To do that we'll make use of the site.php controller that will also act as a fallback when no controller is found for a particular template.

/site/controllers/site.php
<?php

return function ($page, $pages, $site, $kirby) {
    
  # Fetch and store the content for the title tag and the meta description
  $titleTag        = $site->title() . " | " . $page->title();
  $metaDescription = $page->text()->excerpt(120);
    
  # Return an array containing the data that we want to pass to the template
  return compact('titleTag' , 'metaDescription');

};

Now that the logic to fetch the correct data has been moved inside the controller we can simplify the header.php snippet.

/site/snippets/header.php
<!DOCTYPE html>
<html>
<head>
    
  <!-- The title tag we show the title of our site and the title of the current page -->
  <title><?= $titleTag ?></title>

  <!-- The meta description shows an excerpt of the main text -->
  <meta name="description" content="<?= $metaDescription ?>">

</head>
<body>

Excellent, this is already looking a lot cleaner. Now we need to implement the different logic to create the <title> tag for the homepage. To do that we'll use a home.php controller. You'd think that doing something like this would be enough to achieve our goal.

/site/controllers/home.php
<?php

return function ($page, $pages, $site, $kirby) {
    
  # Store the content for the different title tag
  $titleTag = $site->title() . " | " . $site->tagline();
    
  # Return the array containing the data that we want to pass to the template
  return compact('titleTag');

};

But if we do that and we navigate to our homepage we'll get presented with an error.

Undefined variable: metaDescription

That's because we now have a controller for the homepage, and Kirby will use that instead of our fallback one. An intuitive solution would be to simply copy and paste the same line of code from the site.php controller into the home.php controller but that goes against the DRY principle.

Setting up the shared controller

This is where the concept of the shared controller becomes useful. Rather than copy and pasting the same code, we'll first use the $kirby->controller() method to get the data from the site.php controller and store it in a variable.

/site/controllers/home.php
# Grab the data from the default controller
$shared = $kirby->controller('site' , compact('page', 'pages', 'site', 'kirby'));

More info on the controller() method can be found in the docs.

Then we'll overwrite the content of our title tag with the different bit of logic we want to use for the homepage.

/site/controllers/home.php
# Fetch and store the content for the different title tag
$titleTag = $site->title() . " | " . $site->tagline();

And finally, we'll merge the content and return it as an array that will then get passed to the template

/site/controllers/home.php
# Return the array containing the data that we want to pass to the template
return A::merge($shared , compact('titleTag'));

Be careful, when merging the two arrays, to pass the $shared array as the first parameter to the A::merge() function. This is important because otherwise your new data won't overwrite the content coming from the default controller.

Final shared controller

The final home.php controller will look like this:

/site/controllers/home.php
<?php

return function ($page, $pages, $site, $kirby) {
    
  # Grab the data from the default controller
  $shared = $kirby->controller('site' , compact('page', 'pages', 'site', 'kirby'));

  # Fetch and store the content for the different title tag
  $titleTag = $site->title() . " | " . $site->tagline();
    
  # Return the array containing the data that we want to pass to the template
  return A::merge($shared , compact('titleTag'));

};

And that's it. We now have a place to store all the data that is shared among all our templates and we have a way, using controllers, to overwrite what needs to be overwritten on a template by template basis.

Closing thoughts

In this example we've used the site.php controller to share content across the entire site but the same concept can be adapted to share content across a more local scope.

For example, in a blog you could have different controllers for different type of blog posts...

  • controllers
    • post.php
    • post-video.php
    • post-image.php

...and you can leverage the method described in this guide to share the content of the post.php controller with the other controllers.

To do that you need to pass the correct controller name to the controller() function and you're done.

/site/controllers/post-video.php
<?php

return function ($page, $pages, $site, $kirby) {
    
  # Grab the data from the post.php controller
  $post = $kirby->controller('post' , compact('page', 'pages', 'site', 'kirby'));

  # Custom content for the video post
  $video = $page->videourl();
    
  # Return the array containing the data that we want to pass to the template
  return A::merge($post , compact('video'));

};