Getting better code completion in your editor with Kirby

Looking at the forum FAQ it looks like linking to your own article is okay if itā€™s relevant to the community, so here goes. Iā€™ll try to summarize the article here so busy readers donā€™t have to click through!

Kirby 3 is using PHP 7ā€™s type annotations extensively in its source code, but oftentimes your code editor or IDE cannot show you that information because it doesnā€™t know what $page or $kirby means in different contexts, like templates and controllers.

I wrote an article on type hints for Kirby which explains the explains the problem ā€” basically, Kirby is a bit too magical for your IDEā€™s taste ā€” and some solutions I found.

Type hints for templates

We can use PHPDoc syntax to declare the type of the variables injected by Kirby or by your controllers:

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

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

$htmlTitle = $page->title() . ' ā€“ ' . $site->title();

Now we can work with the $page variable and a good PHP IDE will offer autocompletions, parameter names and types, etc.

Type annotations for controllers

Since controllers are functions, we can use type annotations in the function signature:

<?php // site/controllers/article.php

use Kirby\Cms\Page;
use Kirby\Cms\Site;

return function (Page $page, Site $site) {
  return [
    'htmlTitle' => $page->title() . ' ā€“ ' . $site->title()
  ];
}

Okay but whatā€™s a good code editor for this?

Iā€™ve mostly used PhpStorm, which is great but not cheap. Other IDEs like Netbeans or Visual Studio might work well too.

VS Code doesnā€™t have much PHP support out of the box, but I tried the PHP Intelephense extension and it seems to work well (make sure to follow the ā€œQuick Startā€ steps).

5 Likes

Hi!

I just tried this for Visual Code v1.71.2 and PHP Intelephense v1.8.2 with PHP8. It can resolve some Kirby 3.7 helpers but variables like $page are not resolved in my controllers or templates within the PHP open/end tags. Is there nowadays a way to get this to work without adding Type hints or Type Annotations by hand in your custom code? If I do that, resolving the variable works but of course it adds additional, repetetive work. Would it help to have a PHP Intelephense license for the full feature set?

Thank you for your thread and thanks to everyone who helps!

Best Regards

Donā€™t know, if youā€™re still looking, but just found this by @bnomei, which works like a charmā€¦

Add it to the top of your template/snippet.

<?php
/** @var Kirby\Cms\App $kirby */
/** @var Kirby\Cms\Site $site */
/** @var Kirby\Cms\Page $page */
$page->children(); // <-- works now
1 Like

Yes thatā€™s basically the information I posted in the first post of this thread. :wink: See the ā€œType hints for templatesā€ part.

Not that I know of. If $kirby, $site or $page were global variables, it could be enough to use PHPDoc to declare them in one place:

<?php
/** @global Kirby\Cms\App $kirby */
/** @global Kirby\Cms\Site $site */
/** @global Kirby\Cms\Page $page */

And tools like Intelephense or PHPStorm might be clever enough to see that and apply those definitions in all files.

But Kirby does not declare those variables as globals. Instead, it provides those variables in templates, or provides them to specific functions (like controllers). If you lie to your type intelligence system by saying theyā€™re globals, youā€™ll probably end up writing bugs (like trying to use $kirby in a config file).

Something that can help is using Kirbyā€™s helper functions, which are available in templates and other contexts:

  • Instead of $page, use page()
  • Instead of $site, use site()
  • Instead of $kirby, use kirby()

In my experience, Intelephense and PhpStorm both recognize those functions and understand their return types, so you get the same benefit as declaring the type of $page, $site and $kirby, but in a less verbose way.

Personally I still use the /** @var */ declarations in templates, mostly because I have custom models for every template, and page() only gives me the parent Kirby\Cms\Page class, not my subclass. So I do stuff like:

<?php
/** @var PortfolioPage $page */
echo $page->someCustomFunction();
2 Likes

Sorry for not properly reading your original post - and thank you for the detailed answer. Learnt something again today and it might be of value for those, who find this thread :+1::pray:t3:

1 Like

Wonā€™t fix the template issue but might be useful.

4 Likes

Hey, sorry for digging up this old post. Iā€™ve read your post and the comments, and done some other research unsuccessfully. I want to show Kirby to some of my coworkers that only use PHPStorm. I canā€™t seem to get good type hints to work in the snippets + templates files.

Adding these declarations at the start of the files helps a bit:

<?php
  /** @var Kirby\Cms\App $kirby */
  /** @var Kirby\Cms\Site $site */
  /** @var Kirby\Cms\Page $page */
?>

But then PHPStorm sends warnings about other issues like these:

Iā€™ve done some reading and googling but cannot seem to figure out how to get PHPStorm to stop struggling with kirby in these two folders. I previously only used VSCode with PHP Intellisense and never encountered this problem. Anybody got some tips?

You can avoid the issues with the field methods if you make a habit of replacing the magic calls title, myFieldName etc., with the longer version `$page->content()->get(ā€˜titleā€™)->esc(). And then use @lukaskleinschmidtā€™s plugin to take care of some other issues. Itā€™s partly a problem with these magic methods and partly a problem with methods hidden away in arrays/aliases, and other problems with missing type hinting in the core.

We are working on improving type hinting in the core, donā€™t know if that will fix all issues in the future, but it will definitely get better.

2 Likes

Thanks @texnixe for the explanation, I understand the problem now.

$page->title()->esc()
becomes
$page->content()->get('title')->value()->escape()

Iā€™ll explain this to the PHPStorm users. Iā€™ll stick to VSCode for my own Kirby projects :wink: Thanks!

Yeah I think the main issues with type hints in Kirby are:

  • Method overloading, especially for content fields. This can be bypassed by using $page->content()->get('field_name') instead of $page->field_name().
  • Registering new methods for blocks, pages, etc. in plugins (see block methods, page methods), since those get added to some registries dynamically and no static analysis tool can discover them (unless itā€™s specifically written for Kirby plugins). No workarounds here Iā€™m afraid.
  • Controllers can inject random data that static analysis will not know about. Workaround: declare those variables with /** @var */ in the template (but thatā€™s duplicating information and can get out-of-sync with changes in the controller).
  • Collections can return arbitrary data that static analysis doesnā€™t know about.
  • etc.

Iā€™m working on a Rails site these days, and Iā€™m basically having the same issues due to all the auto-loading and convention-over-configuration approach which makes code very hard to figure out for static analysis tools (and Iā€™d argue, also for lowly humans such as myself).

When it comes to Kirby, on the last project I did, I tried to use methods of code organization and code reuse that can be typed:

  • Using page models, instead of registering page methods.
  • Only use controllers for HTTP stuff (e.g. redirecting or returning an error), not to prepare data that will be rendered in the template. Instead, the template is responsible for querying data (from the page itself or related pages), using methods from the page model.

The main exception was collections, which I did use to structure commonly reused list of content. Those did have types in docblocks, but calling kirby()->collection('my-collection') breaks that chain. Maybe I should write my own collection loading code. Or just switch to adding methods to a DefaultPage model and call it a day.

Oh, by the way, I was looking for this static analysis tool for PHP type hints (supporting type hints, phpdoc and more features) that I remembered existing, and it took me way too long to locate it, so here it is if thatā€™s useful for anyone: https://psalm.dev/

@fvsch or @texnixe Do either of you think that writing a Psalm plugin for Kirby would alleviate some or all of the trickier issues? I have liked using Psalm in the past, but beyond that I donā€™t know much about static analysis and how it can or canā€™t handle some of the more ā€œmagicalā€ things Kirby seems to be doing.

This sounds excellent! Do you have any sense on how much this will fix? Is it possible that this is enough on its own, or would a Psalm plugin still be needed to properly tell it about custom autoloaders, parsing the txt files, etc.?