Skip to content

Conversation

@distantnative
Copy link
Member

@distantnative distantnative commented Jul 13, 2025

Description

This PR changes App::controller() to also allow controllers noted in subfolders inside site/controllers. This change enables us to add snippet and block controllers (https://feedback.getkirby.com/213) via a custom plugin then:

<?php

use Kirby\Cms\App;
use Kirby\Template\Snippet;
use Kirby\Toolkit\Str;

class SnippetWithController extends Snippet
{
  protected static $controllers = [];

  public static function controller(
    string|null $file,
    array $data = []
  ): array {
    if (
      $file === null ||
      str_starts_with($file, static::root()) === false
    ) {
      return $data;
    }

    if (isset(static::$controllers[$file])) {
      return array_replace_recursive(
        $data,
        static::$controllers[$file]
      );
    }

    $name = ltrim(Str::before(Str::after($file, static::root()), '.php'), '/');

    // if the snippet has a name, we can load the controller
    // and merge the data with the controller's data
    if ($name !== null) {
      $data = array_replace_recursive(
        $data,
        static::$controllers[$file] = App::instance()->controller('snippets/' . $name, $data)
      );
    }

    return $data;
  }

  public static function factory(
    string|array|null $name,
    array $data = [],
    bool $slots = false
  ): static|string {
    $file = $name !== null ? static::file($name) : null;
    $data = static::controller($file, $data);
    return parent::factory($name, $data, $slots);
  }

  public function render(array $data = [], array $slots = []): string
  {
    $data = array_replace_recursive(
      static::controller($this->file, $data),
      $data
    );

    return parent::render($data, $slots);
  }
}

App::plugin('getkirby/snippet-controllers', [
  'components' => [
    'snippet' => function (
      App $kirby,
      string|array|null $name,
      array $data = [],
      bool $slots = false
    ): Snippet|string {
      return SnippetWithController::factory($name, $data, $slots);
    },
  ]
]);

With this plugin place, one can add a controller for e.g. site/snippets/header.php to site/controllers/snippets/header.php. And since blocks are also just snippets: site/controllers/snippets/blocks/video.php (though this currently just works if a custom blocks snippet has been added, not for the default ones).

Choosing to go down the plugin path as it can have quite the performance impact if every snippet checks for the existence of a controller. Maybe you have also ideas how to improve that performance.

Changelog

Refactored

  • Support App::controller() to return controllers nested in subfolders inside site/controllers

Ready?

  • In-code documentation (wherever needed)
  • Unit tests for fixed bug/feature

For review team

  • Add changes & docs to release notes draft in Notion

@distantnative distantnative added this to the 5.1.0 milestone Jul 13, 2025
@distantnative distantnative self-assigned this Jul 13, 2025
@distantnative distantnative requested a review from a team July 13, 2025 09:58
@distantnative distantnative marked this pull request as ready for review July 13, 2025 09:58
@bastianallgeier bastianallgeier merged commit c0ebc14 into develop-minor Jul 14, 2025
12 checks passed
@bastianallgeier bastianallgeier deleted the refact/controllers-in-folders branch July 14, 2025 10:41
@jensscherbl
Copy link

Nice! I ended up not using App::controller() and instead using Controller::load directly because of this limitation when playing with the idea on my own a while back.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants