PHP code typing with Kirby CMS

software cms php kirby

As I was updating this site and writing my previous post on Kirby CMS, I sometimes struggled to get good code autocompletion.

Kirby 3 is built with PHP 7 and uses type annotations extensively, but this often won’t be reflected in an IDE. This post explores why, and what workarounds you can use.

Why you might want completions

Unlike CMSes like WordPress and Drupal which come with default themes that generate HTML code for you out of the box, Kirby uses a very hands-off approach to building web pages. You can start from scratch and write your own PHP controllers and templates to render what you want, using Kirby’s PHP API.

That’s great if you want to build something bespoke. But it also means that you’re going to spend a lot of time looking at the reference documentation. Before you know it, you’re juggling between your code editor and 15 open documentation tabs.

Screenshot of the Kirby Reference page for the $page object

That’s where code completion — sometimes called “intellisense” — helps. It lets you be a bit lazy and access a lot of that reference information directly in your code editor, as you type.

Ideally, when your editor sees a PHP variable like $page in a Kirby template, it should known that $page is an instance of the Kirby\Cms\Page class. And it should offer code completions and suggestions from that class. No need to check the docs!

Sadly, this doesn’t work out of the box with Kirby, in a few common situations.

Runtime magic

Kirby tries to be approchable for people who are not pro developers or PHP programmers. Its creator, Bastian Allgeier, is a designer and developer, and wants to cater to a wider audience of designers, webmasters, tinkerers, etc. So Kirby tries to do some magic things like:

  1. Load templates (and controllers, if any) by file name. Follow the convention, and your code will run[^2].
  2. Automatically inject variables in your templates and in your controllers.

This means that the template for a page of type 'article' can be named site/templates/article.php and be as simple as:

<h1><?= $page->title() ?></h1>
<?= $page->text() ?>

The downside of this approach is that your code editor has no information about this $page variable. It cannot know if it’s a string, a number, or an object (and what kind of object). Your editor might even warn you that this variable was never defined, and highlight it as an error!

Kirby controllers use a different kind of magic. Controllers are defined as functions returned by a script, such as site/controllers/article.php:

<?php
return function ($page, $site) {
  // Inject two new variables, $content and $articles,
  // in the 'article' template
  return [
    'content' => $page->text()->markdown(),
    'articles' => $site->find('blog')->children()
  ];
}

See the $page and $site parameters in the function? Kirby will look at the function’s parameter names and automatically provide an instance of the current page for a parameter named $page, of the Site object for a parameter named $site, etc.

This lets us ask for a few built-in objects in any order we want, for example:

return function ($site, $kirby, $page) { … }

But again, the downside is that this is magic happening deep inside Kirby’s own code, and your IDE will have no idea what those variables are. No code completion for you!

Adding type hints and annotations

I’ve found a couple solution to this problem.

For controllers, since we’re using function parameters to “request” specific objects, we can add type declarations using PHP’s syntax:

<?php // site/controllers/article.php
use Kirby\Cms\App;
use Kirby\Cms\Page;
use Kirby\Cms\Site;

return function (Site $site, App $kirby, Page $page) {
  …
}

Note that for backward compatibility with Kirby 2, Kirby 3 provides shorter class aliases, including Kirby (for Kirby\Cms\App), Page and Site. Personally, I like using the longer, more explicit class names.

Now, let’s talk about templates. Templates will receive variables defined by Kirby (like the $page and $kirby objects), and any other variable you return in a controller.

Sadly there is no way in PHP to declare “hey I know that this $page variable exists in this context, and I’m going to tell you what it is”. Unlike in TypeScript where you could write something like:

declare var page: Page;

The good news is, there is a widely supported way to declare variable types in PHP, using the PHPDoc @var tag:

<?php // site/templates/article.php

/** @var Kirby\Cms\Page $page */

<h1><?= $page->title() ?></h1>
<?= $page->text() ?>

PHPDoc comments are not a native PHP feature, so declaring a type this way won’t affect how your code runs at all. But most code editors and IDEs with good PHP support will understand this special comment syntax, and will read it as “there is a variable $page in the current scope, and it’s an instance of the Kirby\Cms\Page class”.

And if you have created a Page Model for this template, you can declare the type using the page model’s class name, for example:

<?php // site/models/article.php

class ArticlePage extends Kirby\Cms\Page {
  public function getArticleBody(): string {
    if ($this->content()->body()->isNotEmpty()) {
      return $this->content()->body()->markdown();
    }
    return '';
  }
}
<?php // site/templates/article.php

/** @var ArticlePage $page */

<h1><?= $page->title() ?></h1>
<?= $page->getArticleBody() ?>

Making sure your editor has good PHP support

Finally, if you want to see code completions in your editor or IDE of choice, type annotations may not be enough. You also need an editor or IDE with good PHP support!

Personally, I use PhpStorm when writing more than a couple lines of PHP, because it just works. The downside is that it’s paid software, and not cheap.

I’ve also tried using Visual Studio Code with the PHP Intelephense extension. It works well, but do follow the “Quick Start” steps in the extension’s page.

There are a few other options, such as NetBeans, but I haven’t tried them out.