This blog has been running on Kirby CMS for 6 years. And I’ve been migrating it from Kirby 2 to Kirby 3, which is taking a bit of work.
I like to do a bunch of custom things with my site’s structure and content, so instead of relying on “the Kirby way” I’m implementing a bunch of stuff myself.
That means writing PHP code that queries and manipulates data — often called “logic” code by programmers — and I’ve struggled a bit with understanding where I can put this code in the project.
Logic in templates? Sure.
One thing I like about Kirby is that it’s powerful and doesn’t require you to jump through hooks. So if you want to put all your logic in a template file, you can!
In the following example, we’re querying all the child pages of the current page, keeping the “Published” ones and sorting them by date, then listing the latest 20 pages.
<?php // site/templates/blog.php
$posts = $page->children()
->filterBy('isPublished', true)
->orderBy('date', 'desc')
->limit(20);
?>
<h1><?= $page->title() ?></h1>
<ul>
<?php foreach($posts as $item): ?>
<li><?= $item->title() ?></li>
<?php endforeach; ?>
</ul>
(This template would be used for any page which has the 'blog'
content type, meaning its content file is something like content/my-page/blog.txt
. See “Creating pages” in the Kirby documentation for more information.)
Or in controllers, maybe?
Putting logic in template is great for people just getting started with Kirby, or people who may not be professional programmers. But if you want to separate your logic from the template, you can move it to a controller function:
<?php // site/controllers/blog.php
return function ($page) {
$posts = $page->children()
->filterBy('isPublished', true)
->orderBy('date', 'desc')
->limit(20);
return ['posts' => $posts];
};
But what if you want to share some logic between different templates, controllers or page types?
It would be awesome if you could call something like $page->getPublishedChildren()
, from anywhere.
Kirby offers two ways to do that:
Creating a page method
Page methods apply to all $page
objects, regardless of page type.
They’re a slightly older technique in Kirby, and in Kirby 3 you need to declare a plugin to add page methods:
<?php // site/plugins/my-plugin/index.php
Kirby::plugin('me/my-plugin', [
'pageMethods' => [
'getPublishedChildren' => function() {
return $this->children()
->filterBy('isPublished', true)
->orderBy('date', 'desc')
->limit(20);
}
]
]);
There are a few downsides to page methods:
- Having to register a plugin is a bit awkward.
- If you’re using PHP types and intellisense in your editor, your editor has no way to know that
$this
in your page method is an instance of Kirby’sPage
class. That means you don’t get code completions and validation in your editor.
Creating a page model
The newer option is to use page models, which are defined as PHP classes extending Kirby’s Page
class.
<?php // site/models/blog.php
class BlogPage extends Page {
public function getPublishedChildren() {
return $this->children()
->filterBy('isPublished', true)
->orderBy('date', 'desc')
->limit(20);
}
}
This looks a little bit cleaner to me, mainly because we avoid creating a plugin.
And since we’re extending the Page
class, now our code editors will know what methods the $this
object has1.
And reading the Kirby documentation, and especially the Guide, it looks like Page Models are the preferred mechanism for adding functionality to page objects.
But they have one big downside: they only apply to a given page type, the 'blog'
page type in this example.
If you try to use $page->getPublishedChildren()
on a page with a different type, you get an error.
Are we forced to use Page Methods in a plugin to share functionality between pages? Well, maybe not.
Creating a default page model
Our page models already inherit from Kirby’s Page
class.
What if we added one more class to that hierarchy?
Let’s start by moving our method to a new class, which we’ll call DefaultPage
.
Ideally we should pick a name that doesn’t match a page type (meaning we don’t have pages with a content file named default.txt
).
<?php // site/models/default.php
class DefaultPage extends Page {
public function getPublishedChildren() {
/* ... */
}
}
Then we can create a class for our 'blog'
page type, which extends DefaultPage
:
<?php // site/models/blog.php
require_once 'default.php';
class BlogPage extends DefaultPage {
// Add methods specific to the 'blog' type
}
Note that we have to require default.php
explicitly to load the DefaultPage
class,
because Kirby won’t load it automatically since it doesn’t match a real page type.
If we add a new page type, we can create an empty page model that inherits from DefaultPage
,
and it will inherit the same shared methods.
<?php // site/models/article.php
require_once 'default.php';
class ArticlePage extends DefaultPage {}
Fair warning: this means that to fully emulate a global “page method”, we have to create a page model for every single page type we have!
If that’s too verbose, your options are:
- Going back to using the page method technique;
- Upvoting this feature request.
Happy coding!