diff --git a/README.md b/README.md index cf26e21..9a88fe4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,111 @@ -# kirby-nitro +# ⛽️Kirby Nitro -monkey patches to make kirby faster. use at your own risk. +![Release](https://flat.badgen.net/packagist/v/bnomei/kirby-nitro?color=ae81ff) +![Downloads](https://flat.badgen.net/packagist/dt/bnomei/kirby-nitro?color=272822) +[![Build Status](https://flat.badgen.net/travis/bnomei/kirby-nitro)](https://travis-ci.com/bnomei/kirby-nitro) +[![Maintainability](https://flat.badgen.net/codeclimate/maintainability/bnomei/kirby-nitro)](https://codeclimate.com/github/bnomei/kirby-nitro) +[![Twitter](https://flat.badgen.net/badge/twitter/bnomei?color=66d9ef)](https://twitter.com/bnomei) + +Nitro speeds up the loading of content in your Kirby project. + +## Commercial Usage + +>
+> Support open source!

+> This plugin is free but if you use it in a commercial project please consider to sponsor me or make a donation.
+> If my work helped you to make some cash it seems fair to me that I might get a little reward as well, right?

+> Be kind. Share a little. Thanks.

+> ‐ Bruno
+>   + +| M | O | N | E | Y | +|------------------------------------------------------|---------------------------------------|-------------------------------------------------|-----------------------------------------------------|----------------------------------------------| +| [Github sponsor](https://github.com/sponsors/bnomei) | [Patreon](https://patreon.com/bnomei) | [Buy Me a Coffee](https://buymeacoff.ee/bnomei) | [Paypal dontation](https://www.paypal.me/bnomei/15) | [Hire me](mailto:b@bnomei.com?subject=Kirby) | + +## Installation + +- unzip [master.zip](https://github.com/bnomei/kirby-nitro/archive/master.zip) as folder `site/plugins/kirby-nitro` + or +- `git submodule add https://github.com/bnomei/kirby-nitro.git site/plugins/kirby-nitro` or +- `composer require bnomei/kirby-nitro` + +## Setup + +For each template you want to be cached you need to use a model to add the content cache logic using a trait. + +**site/models/default.php** + +```php +class DefaultPage extends \Kirby\Cms\Page +{ + use \Bnomei\ModelWithNitro; +} +``` + +> [!NOTE] +> You can also use the trait for user models. File models are patched automatically. + +## Using the Cache + +You can use the single-file-based cache of nitro to store your own key-value pairs, just like with a regular cache in +Kirby. Since the Nitro cache is fully loaded with every request I would not advise to store too many big chunks of +data (like HTML output). + +```php +nitro()->cache()->set('mykey', 'value'); +nitro()->cache()->set('mykey', 'value', 1); + +$value = nitro()->cache()->get('mykey'); +$value = nitro()->cache()->getOrSet('mykey', fn() => 'value'); +``` + +## Using the Cache Driver in Kirby + +You can also use the singe-file-based cache of Nitro as a **cache driver** for Kirby. This will allow you to use it for +caching of other extensions in Kirby. I would highly recommend to use it for Kirby's UUID cache. + +**site/config/config.php** + +```php +return [ + // ... other options + + // use nitro as cache driver for storing uuids + // instead of the default file-based cache + 'cache' => [ + 'uuid' => [ + 'type' => 'nitro', + ], + ], + + // example: in Lapse plugin + 'bnomei.lapse.cache' => [ + 'type' => 'nitro', + ], +]; +``` + +## Settings + +| bnomei.nitro. | Default | Description | +|-------------------|-----------------------|------------------------------------------------------------------------------| +| auto-clean-cache | `true` | will clean the cache once before the first get() | +| patch-dir-class | always on | monkey-patch the \Kirby\Filesystem\Dir class to use Nitro for caching | +| patch-files-class | `true` | monkey-patch the \Kirby\CMS\Files class to use Nitro for caching its content | +| max-dirty-cache | `512` | write every N changes or on destruct | +| json-encode-flags | `JSON_THROW_ON_ERROR` | | +| model.read | `true` | read from cache for all models that use the ModelWithNitro trait | +| model.write | `true` | write to cache for all models that use the ModelWithNitro trait | + +## Disclaimer + +This plugin is provided "as is" with no guarantee. Use it at your own risk and always test it yourself before using it +in a production environment. If you find any issues, +please [create a new issue](https://github.com/bnomei/kirby-nitro/issues/new). + +## License + +[MIT](https://opensource.org/licenses/MIT) + +It is discouraged to use this plugin in any project that promotes racism, sexism, homophobia, animal abuse, violence or +any other form of hate speech. diff --git a/classes/Nitro.php b/classes/Nitro.php index d68b038..104f3fc 100644 --- a/classes/Nitro.php +++ b/classes/Nitro.php @@ -23,6 +23,7 @@ public function __construct(array $options = []) 'enabled' => true, 'cacheDir' => realpath(__DIR__.'/../').'/cache', 'cacheType' => 'json', + 'json-encode-flags' => JSON_THROW_ON_ERROR, ], $options); foreach ($this->options as $key => $value) { diff --git a/classes/Nitro/DirInventory.php b/classes/Nitro/DirInventory.php index bbc9f97..1f1a00d 100644 --- a/classes/Nitro/DirInventory.php +++ b/classes/Nitro/DirInventory.php @@ -18,6 +18,7 @@ class DirInventory public function __construct(array $options = []) { + // can not use option() as it might run before Kirby is loaded $this->options = $options; $this->isDirty = false; $this->data = []; @@ -39,7 +40,7 @@ public function __destruct() opcache_invalidate($file); } } else { - F::write($file, json_encode($this->data)); + F::write($file, json_encode($this->data, $this->options['json-encode-flags'])); } } diff --git a/classes/Nitro/SingleFileCache.php b/classes/Nitro/SingleFileCache.php index 806ab9d..63d0f50 100644 --- a/classes/Nitro/SingleFileCache.php +++ b/classes/Nitro/SingleFileCache.php @@ -21,6 +21,7 @@ public function __construct(array $options = []) $this->options = array_merge([ 'auto-clean-cache' => option('bnomei.nitro.auto-clean-cache'), + 'json-encode-flags' => option('bnomei.nitro.json-encode-flags'), 'max-dirty-cache' => (int) option('bnomei.nitro.max-dirty-cache'), 'debug' => option('debug'), ], $options); @@ -56,6 +57,14 @@ public function set(string $key, $value, int $minutes = 0): bool */ $key = $this->key($key); + + // flatten kirby fields + $value = $this->serialize($value); + + // make sure the value can be stored as json + // if not fail here so a trace is more helpful + $value = json_decode(json_encode($value, $this->options['json-encode-flags']), true); + $this->data[$key] = (new Value($value, $minutes))->toArray(); $this->isDirty++; if ($this->isDirty > $this->options['max-dirty-cache']) { @@ -142,14 +151,43 @@ private function file() return kirby()->cache('bnomei.nitro.sfc')->root().'/single-file-cache.json'; } - private function write(): bool + public function write(): bool { if ($this->isDirty === 0) { return false; } - F::write($this->file(), json_encode($this->data)); + F::write($this->file(), json_encode($this->data, $this->options['json-encode-flags'])); $this->isDirty = 0; return true; } + + private static function isCallable($value): bool + { + // do not call global helpers just methods or closures + return ! is_string($value) && is_callable($value); + } + + public function serialize($value) + { + if (! $value) { + return null; + } + $value = self::isCallable($value) ? $value() : $value; + + if (is_array($value)) { + $items = []; + foreach ($value as $key => $item) { + $items[$key] = $this->serialize($item); + } + + return $items; + } + + if (is_a($value, 'Kirby\Content\Field')) { + return $value->value(); + } + + return $value; + } } diff --git a/composer.json b/composer.json index 4e77e0b..5515f7f 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "bnomei/kirby-nitro", "type": "kirby-plugin", "version": "1.0.0", - "description": "", + "description": "Nitro speeds up the loading of content in your Kirby project.", "license": "MIT", "authors": [ { @@ -69,5 +69,10 @@ }, "conflict": { "bnomei/kirby3-boost": "*" + }, + "suggest": { + "bnomei/kirby-blueprints": "PHP Class-based Blueprints for Kirby CMS for better type safety and code completion.", + "bnomei/kirby3-lapse": "Cache any data until set expiration time (with automatic keys).", + "getkirby/staticache": "Static site performance on demand!" } } diff --git a/index.php b/index.php index 963627e..b183b47 100644 --- a/index.php +++ b/index.php @@ -18,6 +18,7 @@ function nitro(): \Bnomei\Nitro 'patch-files-class' => true, 'auto-clean-cache' => true, 'max-dirty-cache' => 512, // write every N changes or on destruct + 'json-encode-flags' => JSON_THROW_ON_ERROR, // | JSON_INVALID_UTF8_IGNORE, 'model' => [ 'read' => true, 'write' => true, @@ -32,8 +33,32 @@ function nitro(): \Bnomei\Nitro }, 'page.*:after' => function ($event, $page) { if ($event->action() !== 'render') { - \Bnomei\Nitro::singleton()->flush(); + \Bnomei\Nitro::singleton()->dir()->flush(); } }, ], + 'commands' => [ + 'nitro:index' => [ + 'description' => 'Run Nitro Index', + 'args' => [], + 'command' => static function ($cli): void { + + $kirby = $cli->kirby(); + $kirby->impersonate('kirby'); + + $cli->out('Indexing...'); + $count = nitro()->modelIndex(); + $cli->out($count.' models indexed.'); + + $cli->success('Done.'); + + if (function_exists('janitor')) { + janitor()->data([ + 'status' => 200, + 'message' => $count.' models indexed.', + ]); + } + }, + ], + ], ]); diff --git a/tests/NitroTest.php b/tests/NitroTest.php index 0236f3f..3fa020a 100644 --- a/tests/NitroTest.php +++ b/tests/NitroTest.php @@ -19,6 +19,26 @@ expect($value)->toBe('value'); }); +it('will serialize kirby\content\fields to their value', function () { + $cache = nitro()->cache(); + $cache->set('home.title', page('home')->title()); + $value = $cache->get('home.title'); + + expect($value)->toBe('Home'); +}); + +it('will serialize objects if they support it', function () { + $cache = nitro()->cache(); + $cache->set('object', new \Kirby\Toolkit\Obj([ + 'hello' => 'world', + ])); + $value = $cache->get('object'); + + expect($value)->toBe([ + 'hello' => 'world', + ]); +}); + it('will not use the cache in debug mode', function () { Nitro::$singleton = null;