Skip to content

Commit

Permalink
✨ improved caching
Browse files Browse the repository at this point in the history
  • Loading branch information
bnomei committed Mar 22, 2024
1 parent 965779a commit 31eefd6
Show file tree
Hide file tree
Showing 22 changed files with 249 additions and 36 deletions.
48 changes: 41 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -535,26 +535,60 @@ class ElephantPage extends Page
}
```

## Caching and Lazy-loading of blueprints
## Caching and Lazy-loading of PHP based blueprints

The `Blueprint` attribute allows you to set the `cacheable` and `loadPluginsAfter` properties. Both are disabled by default.

If cached it will only compile the blueprint once and then use the cached version. The cache lasts for the duration of you opcache `opcache.revalidate_freq` settings.
If caching is enabled it will only compile the blueprint once and then use the cached version. The cache lasts for the duration set.

```php
#[
Blueprint(cacheable: true)
// cache for 5 seconds
Blueprint(cache: 5)

// cache with default 60 seconds
Blueprint()
Blueprint(cache: null)

// disable
Blueprint(cache: 0)
]
```

If `loadPluginsAfter` is set to `true` it will compile the blueprint after the kirby instance is ready. This means you can use the `kirby()/site()/page()`-helpers in your blueprints or query the content of the site.
> The default can be set in the config.php of your plugin with the `bnomei.blueprints.expire` option.

If `defer` is set to `true` it will compile the blueprint after the kirby instance is ready. This means you can use the `kirby()/site()/page()`-helpers in your blueprints or query the content of the site. Most of the time it makes sense to disable caching when using `defer` since the cache might interfere with the dynamic behaviour you are trying to create.

```php
#[
Blueprint(loadPluginsAfter: true)
// load with system.loadPlugins:after hook and no cache
Blueprint(defer: true, cache: 0)
]
```

## Caching for PHP and YAML based blueprints

If you are not using the attributes to define your blueprint definitions you can still enable caching using traits model. One to enable the cache and one to define if it should resolve all fields as much as possible.

Resolving the fields means to write all meta-data in extracting them using a model instance. So if you are using the Kirby query-strings language or other dynamic values in your blueprints (like the Janitor or some SEO plugins do), you might want to skip resolving. But... if you have a very complex blueprint with many fields and you want to speed up the blueprint loading you might want to resolve them. Especially when using lots of Layouts, Columns & Blocks resolving might be a good idea.

```php

**site/models/example.php**
```php
<?php

class ExamplePage extends \Kirby\Cms\Page {

// can be used safely to speed up all kind of blueprints
use \Bnomei\Blueprints\HasBlueprintCache;

// not recommended for dynamic blueprints
use \Bnomei\Blueprints\HasBlueprintCacheResolve;

}
```

> The cache will use the duration as defined in the `bnomei.blueprints.expire` option in the config.php of your plugin.
## Disclaimer

Expand Down
2 changes: 1 addition & 1 deletion classes/Blueprints/Attributes/Blueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Blueprint
/**
* Flag method to be parsed as returning a blueprint definition for the model the method belongs to
*/
public function __construct(public bool $cacheable = false, public bool $loadPluginsAfter = false)
public function __construct(public ?int $cache = null, public ?bool $defer = null)
{
}
}
10 changes: 5 additions & 5 deletions classes/Blueprints/Blueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ class Blueprint
{
protected static array $loadPluginsAfter = [];

public function __construct(private readonly string $modelClass, private bool $loadAfter = false, private ?bool $cache = null)
public function __construct(private readonly string $modelClass, private ?bool $defer = null, private ?int $cache = null)
{
$isCacheable = false;
$rc = new ReflectionClass($modelClass);
foreach ($rc->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
foreach ($method->getAttributes() as $attribute) {
if ($attribute->getName() === 'Bnomei\Blueprints\Attributes\Blueprint') {
$isCacheable = $attribute->newInstance()->cacheable;
$this->loadAfter = $attribute->newInstance()->loadPluginsAfter;
$isCacheable = $attribute->newInstance()->cache;
$this->defer = $attribute->newInstance()->defer;
break;
}
}
Expand All @@ -40,7 +40,7 @@ public function __toString(): string

public function toArray(): array
{
$blueprint = $this->cache ? BlueprintCache::get($this->modelClass) : null;
$blueprint = $this->cache ? BlueprintCache::get($this->modelClass, null, $this->cache) : null;
if ($blueprint) {
return $blueprint;
}
Expand Down Expand Up @@ -236,6 +236,6 @@ public static function loadPluginsAfter()

public function isLoadAfter(): bool
{
return $this->loadAfter;
return $this->defer === true;
}
}
57 changes: 48 additions & 9 deletions classes/Blueprints/BlueprintCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,32 +29,46 @@ public static function cacheDir(): ?string
return kirby()->session()->get($key);
}

private static function cacheFile(string $key): ?string
protected static function cacheFile(string $key): ?string
{
$hash = hash('xxh3', $key);
$hash = substr($hash, 0, 2).'/'.substr($hash, 2);

return static::cacheDir() ?
static::cacheDir().'/'.hash('xxh3', $key).'.cache'
static::cacheDir().'/'.$hash.'.cache'
: null;
}

public static function get(string $key, $default = null): ?array
public static function exists(string $key, $expire = null): bool
{
$file = static::cacheFile($key);
if (! $file) {
return $default;
return false;
}
if ($expire === null) {
$expire = option('bnomei.blueprints.expire'); // in seconds
}
$expire = 5; // seconds
if ($opcacheConfig = opcache_get_configuration()) {
$expire = $opcacheConfig['directives']['opcache.enable'] ?
$opcacheConfig['directives']['opcache.revalidate_freq'] : $expire;
if (! $expire || $expire <= 0) {
return false;
}
$m = F::modified($file) + $expire >= time();
if (F::exists($file) && $m) {
return Json::read($file);
return true;
}
if (F::exists($file) && ! $m) {
F::remove($file);
}

return false;
}

public static function get(string $key, $default = null, $expire = null): ?array
{
$file = static::cacheFile($key);
if ($file && static::exists($key, $expire)) {
return Json::read($file);
}

return $default;
}

Expand All @@ -69,4 +83,29 @@ public static function getKey(): string
{
return 'bnomei.blueprints.cache.dir';
}

public static function preloadCachedBlueprints(): void
{
$kirby = kirby();
$preload = $kirby->option('bnomei.blueprints.preload');
if ($preload === false) {
return;
}

$preloaded = [];
foreach ($preload as $type) {
$blueprints = $kirby->blueprints($type);
foreach ($blueprints as $name) {
$key = $type.'/'.$name;
$blueprint = static::get($key);
if ($blueprint === null) {
continue;
}
$preloaded[$key] = $blueprint;
}
}
$kirby->extend([
'blueprints' => $preloaded,
]);
}
}
50 changes: 50 additions & 0 deletions classes/Blueprints/HasBlueprintCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace Bnomei\Blueprints;

use Kirby\Cms\FileBlueprint;
use Kirby\Cms\PageBlueprint;
use Kirby\Cms\UserBlueprint;

trait HasBlueprintCache
{
protected static bool $blueprintCache = false;

public function blueprintCacheKey(): string
{
$blueprint = $this->blueprint();
$type = match ($blueprint::class) {
PageBlueprint::class => 'pages',
FileBlueprint::class => 'files',
UserBlueprint::class => 'users',
};

return $type.'/'.$blueprint->name();
}

public function __destruct()
{
/** @var \Kirby\Cms\ModelWithContent $this */
$key = $this->blueprintCacheKey();
if (static::$blueprintCache) {
return;
}
static::$blueprintCache = true;

if (BlueprintCache::exists($key) === false) {
$blueprint = $this->blueprint();
$data = $blueprint->toArray();
if (isset(static::$blueprintCacheResolve)) {
foreach ($blueprint->toArray()['tabs'] as $tabKey => $tab) {
foreach ($tab['columns'] as $columnKey => $column) {
foreach ($column['sections'] as $sectionKey => $section) {
$section = $blueprint->section($sectionKey);
$data['tabs'][$tabKey]['columns'][$columnKey]['sections'][$sectionKey]['fields'] = $section->toArray()['fields'];
}
}
}
}
BlueprintCache::set($key, $data);
}
}
}
8 changes: 8 additions & 0 deletions classes/Blueprints/HasBlueprintCacheResolve.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Bnomei\Blueprints;

trait HasBlueprintCacheResolve
{
protected static bool $blueprintCacheResolve = true;
}
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "bnomei/kirby-blueprints",
"type": "kirby-plugin",
"version": "4.3.0",
"version": "4.4.0",
"license": "MIT",
"homepage": "https://github.com/bnomei/kirby3-blueprints",
"description": "PHP Class-based Blueprints for Kirby CMS for better type safety and code completion",
Expand Down
3 changes: 3 additions & 0 deletions index.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
Kirby::plugin('bnomei/blueprints', [
'options' => [
'cache' => true,
'expire' => 60, // in seconds, null = use opcache duration, 0 to disable
'preload' => ['pages', 'files', 'users'],
],
'hooks' => [
'system.loadPlugins:after' => function () {
Expand All @@ -13,6 +15,7 @@
// and use it on next request
\Bnomei\Blueprints\BlueprintCache::rememberCacheDir();
\Bnomei\Blueprints\Blueprint::loadPluginsAfter();
\Bnomei\Blueprints\BlueprintCache::preloadCachedBlueprints();
},
],
]);
1 change: 1 addition & 0 deletions tests/content/example-2/example.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Uuid: 2TNU3cFebBEBQm6H
9 changes: 9 additions & 0 deletions tests/content/example/example.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Text:

----

Blocks: [{"content":{"location":"kirby","image":[],"src":"","alt":"","caption":"","link":"","ratio":"","crop":"false"},"id":"bf3ab53f-f5a2-499c-a684-6b74c779f2eb","isHidden":false,"type":"image"},{"content":{"text":""},"id":"f3fda5bc-d4f6-4351-9378-5ec0f36e933a","isHidden":false,"type":"text"},{"content":{"code":"","language":"text"},"id":"cfb1028f-c85e-4434-92c5-d779dfcc7e77","isHidden":false,"type":"code"}]

----

Uuid: 2TNU3cFebBEBQm6H
7 changes: 6 additions & 1 deletion tests/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@
const KIRBY_HELPER_E = false;

require __DIR__.'/../vendor/autoload.php';
echo (new Kirby())->render();

$micotime = microtime(true);
$render = (new Kirby())->render();
$micotime = microtime(true) - $micotime;
header('X-Render-Time: '.number_format($micotime, 3, '.', '').'s');
echo $render;
3 changes: 3 additions & 0 deletions tests/site/blueprints/fields/extme.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
type: text
label: extme
placeholder: extme
7 changes: 7 additions & 0 deletions tests/site/models/example.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

class ExamplePage extends \Kirby\Cms\Page
{
use \Bnomei\Blueprints\HasBlueprintCache;
use \Bnomei\Blueprints\HasBlueprintCacheResolve;
}
6 changes: 6 additions & 0 deletions tests/site/plugins/aaaa/blueprints/blocks/someblo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: Someblo
fields:
title:
label: Title
type: text
text: fields/extme
15 changes: 15 additions & 0 deletions tests/site/plugins/aaaa/blueprints/pages/example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
title: example

fields:
text:
extends: fields/extme
placeholder: "{{ page.title }}"
blocks:
type: blocks
fieldsets:
- text
- image
- video
- quote
- code
- gallery
Loading

0 comments on commit 31eefd6

Please sign in to comment.