Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"require": {
"php": "^8.2",
"league/commonmark": "^2.5.3",
"ext-mbstring": "*"
"ext-mbstring": "*",
"psr/simple-cache": "^3.0"
},
"autoload": {
"psr-4": {
Expand Down
40 changes: 40 additions & 0 deletions docs/caching.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
title: Caching
---

Syntax highlighting can be a resource-intensive operation, especially for large code snippets or when processing many snippets in a short period.

To improve performance, Phiki includes a built-in caching mechanism built around the `psr/simple-cache` interface.

## Enabling caching

Enabling caching is as simple as providing a cache implementation to the `Phiki::cache()` method.

```php
class SimpleCache implements \Psr\SimpleCache\CacheInterface
{
// ...
}

$phiki = (new Phiki)
->cache(new SimpleCache);
```

This will enable caching for all subsequent calls to `codeToHtml()`. The cache will store the generated HTML for each unique combination of code, grammar, theme(s), gutter setting and `Transformer` class.

## Cache invalidation

Phiki does not include any built-in cache invalidation mechanism apart from a change in the cache key.

If you need to invalidate the cache for any reason, you must do so using the methods provided by your chosen cache implementation.

## Caching individual snippets

If you don't want to cache all syntax highlighted code, you can also cache individual `codeToHtml()` calls by passing a cache implementation to the `PendingHtmlOutput::cache()` method.

```php
$html = (new Phiki)
->codeToHtml("<?php echo ...", Grammar::Php, Theme::GithubLight)
->cache(new SimpleCache)
->toString();
```
3 changes: 2 additions & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"pages": [
"installation",
"highlighting-code",
"multi-themes"
"multi-themes",
"caching"
]
},
{
Expand Down
40 changes: 37 additions & 3 deletions docs/laravel.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,49 @@ class AppServiceProvider extends ServiceProvider
}
```

### Caching

Phiki automatically enables caching when used in a Laravel application. It uses your application's default cache store (`CACHE_STORE`) to cache highlighted code blocks.

If you wish to customize the cache store used by Phiki, you can do so in the `boot` method of a service provider.

```php
use Phiki\Adapters\Laravel\Facades\Phiki;
use Illuminate\Support\Facades\Cache;

class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Phiki::cache(Cache::store('redis'));
}
}
```

#### Cache invalidation

Phiki's cache key is based on the content of the code block, the grammar, the themes chosen, the gutter setting, and any transformers used.

If any of these change, Phiki will automatically generate a new cache key and re-highlight the code block.

If you need to manually clear Phiki's cache, you can do so by calling the `php artisan cache:clear` command, which will clear the entire cache for your application.

```sh
php artisan cache:clear
```

## `Str::markdown()`

If you're using the `Str::markdown()` helper method, you can use Phiki's [CommonMark](/commonmark) extension to highlight code blocks inside of your Markdown.

```php
use Illuminate\Support\Str;
use Phiki\Adapters\CommonMark\PhikiExtension;
use Phiki\Phiki;
use Phiki\Theme\Theme;

Str::markdown($markdown, extensions: [
new PhikiExtension(Theme::GithubLight),
new PhikiExtension(Theme::GithubLight, resolve(Phiki::class)),
]);
```

Expand All @@ -72,9 +104,10 @@ As per the documentation on Phiki's [gutter](/highlighting-code), you can enable
use Illuminate\Support\Str;
use Phiki\Adapters\CommonMark\PhikiExtension;
use Phiki\Theme\Theme;
use Phiki\Phiki;

Str::markdown($markdown, extensions: [
new PhikiExtension(Theme::GithubLight, withGutter: true),
new PhikiExtension(Theme::GithubLight, resolve(Phiki::class), withGutter: true),
]);
```

Expand All @@ -85,13 +118,14 @@ As per the documentation on [Multiple themes](/multiple-themes), you can also pa
```php
use Illuminate\Support\Str;
use Phiki\Adapters\CommonMark\PhikiExtension;
use Phiki\Phiki;
use Phiki\Theme\Theme;

Str::markdown($markdown, extensions: [
new PhikiExtension([
'light' => Theme::GithubLight,
'dark' => Theme::GithubDark,
]),
], resolve(Phiki::class)),
]);
```

Expand Down
2 changes: 2 additions & 0 deletions src/Adapters/Laravel/Facades/Phiki.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
* @method static array<int, array<int, \Phiki\Token\HighlightedToken>> tokensToHighlightedTokens(array<int, array<int, \Phiki\Token\Token>> $tokens, string|array|\Phiki\Theme\Theme $theme)
* @method static array<int, array<int, \Phiki\Token\HighlightedToken>> codeToHighlightedTokens(string $code, string|\Phiki\Grammar\Grammar $grammar, string|array|\Phiki\Theme\Theme $theme)
* @method static \Phiki\Output\Html\PendingHtmlOutput codeToHtml(string $code, string|\Phiki\Grammar\Grammar $grammar, string|array|\Phiki\Theme\Theme $theme)
* @method static \Phiki\Environment environment()
* @method static \Phiki\Phiki extend(\Phiki\Contracts\ExtensionInterface $extension)
* @method static \Phiki\Phiki grammar(string $name, string|\Phiki\Grammar\ParsedGrammar $pathOrGrammar)
* @method static \Phiki\Phiki theme(string $name, string|\Phiki\Theme\ParsedTheme $pathOrTheme)
* @method static \Phiki\Phiki cache(\Psr\SimpleCache\CacheInterface $cache)
*
* @see \Phiki\Phiki
*/
Expand Down
3 changes: 2 additions & 1 deletion src/Adapters/Laravel/PhikiServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Phiki\Adapters\Laravel;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\ServiceProvider;
use Phiki\Phiki;

Expand All @@ -13,7 +14,7 @@ class PhikiServiceProvider extends ServiceProvider
*/
public function register(): void
{
$this->app->singleton(Phiki::class, static fn () => new Phiki);
$this->app->singleton(Phiki::class, static fn () => (new Phiki)->cache(Cache::store()));
}

/**
Expand Down
10 changes: 10 additions & 0 deletions src/Environment.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
use Phiki\Theme\ParsedTheme;
use Phiki\Theme\Theme;
use Phiki\Theme\ThemeRepository;
use Psr\SimpleCache\CacheInterface;

class Environment
{
public readonly GrammarRepository $grammars;

public readonly ThemeRepository $themes;

public ?CacheInterface $cache = null;

public function __construct()
{
$this->grammars = new GrammarRepository;
Expand All @@ -42,4 +45,11 @@ public function theme(string $slug, string|ParsedTheme $theme): static

return $this;
}

public function cache(CacheInterface $cache): static
{
$this->cache = $cache;

return $this;
}
}
33 changes: 33 additions & 0 deletions src/Output/Html/PendingHtmlOutput.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Phiki\Theme\ParsedTheme;
use Phiki\Token\HighlightedToken;
use Phiki\Token\Token;
use Psr\SimpleCache\CacheInterface;
use Stringable;

class PendingHtmlOutput implements Stringable
Expand All @@ -23,6 +24,8 @@ class PendingHtmlOutput implements Stringable

protected ?Closure $highlightTokensUsing = null;

protected ?CacheInterface $cache = null;

protected array $transformers = [];

protected int $startingLineNumber = 1;
Expand Down Expand Up @@ -56,6 +59,13 @@ public function highlightTokensUsing(Closure $callback): self
return $this;
}

public function cache(?CacheInterface $cache): self
{
$this->cache = $cache;

return $this;
}

public function withGutter(bool $withGutter = true): self
{
$this->withGutter = $withGutter;
Expand All @@ -82,6 +92,19 @@ public function toString(): string
return $this->__toString();
}

public function cacheKey(): string
{
return 'phiki_html_' . md5(serialize([
$this->code,
$this->grammar->scopeName,
array_keys($this->themes),
...array_map(fn (ParsedTheme $theme) => $theme->name, $this->themes),
$this->withGutter,
$this->startingLineNumber,
...array_map(fn (TransformerInterface $transformer) => get_class($transformer), $this->transformers),
]));
}

protected function callTransformerMethod(string $method, mixed ...$args): mixed
{
if ($this->transformers === []) {
Expand Down Expand Up @@ -109,6 +132,12 @@ private function getDefaultThemeId(): string

public function __toString(): string
{
$cacheKey = $this->cacheKey();

if (isset($this->cache) && $this->cache->has($cacheKey)) {
return $this->cache->get($cacheKey);
}

[$code] = $this->callTransformerMethod('preprocess', $this->code);
[$tokens] = $this->callTransformerMethod('tokens', call_user_func($this->generateTokensUsing, $code, $this->grammar));
[$highlightedTokens] = $this->callTransformerMethod('highlighted', call_user_func($this->highlightTokensUsing, $tokens, $this->themes));
Expand Down Expand Up @@ -197,6 +226,10 @@ public function __toString(): string
[$root] = $this->callTransformerMethod('root', new Root([$pre]));
[$html] = $this->callTransformerMethod('postprocess', $root->__toString());

if (isset($this->cache)) {
$this->cache->set($cacheKey, $html);
}

return $html;
}
}
9 changes: 9 additions & 0 deletions src/Phiki.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Phiki\TextMate\Tokenizer;
use Phiki\Theme\ParsedTheme;
use Phiki\Theme\Theme;
use Psr\SimpleCache\CacheInterface;

class Phiki
{
Expand Down Expand Up @@ -55,6 +56,7 @@ public function codeToHighlightedTokens(string $code, string|Grammar $grammar, s
public function codeToHtml(string $code, string|Grammar $grammar, string|array|Theme $theme): PendingHtmlOutput
{
return (new PendingHtmlOutput($code, $this->environment->grammars->resolve($grammar), $this->wrapThemes($theme)))
->cache($this->environment->cache)
->generateTokensUsing(fn (string $code, ParsedGrammar $grammar) => $this->codeToTokens($code, $grammar))
->highlightTokensUsing(fn (array $tokens, array $themes) => $this->tokensToHighlightedTokens($tokens, $themes));
}
Expand Down Expand Up @@ -88,4 +90,11 @@ public function theme(string $name, string|ParsedTheme $pathOrTheme): static

return $this;
}

public function cache(CacheInterface $cache): static
{
$this->environment->cache($cache);

return $this;
}
}
63 changes: 63 additions & 0 deletions tests/Fixtures/FakeCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace Phiki\Tests\Fixtures;

use Psr\SimpleCache\CacheInterface;

class FakeCache implements CacheInterface
{
private array $store = [];

public function get($key, $default = null): mixed
{
return $this->store[$key] ?? $default;
}

public function set($key, $value, $ttl = null): bool
{
$this->store[$key] = $value;
return true;
}

public function delete($key): bool
{
unset($this->store[$key]);
return true;
}

public function clear(): bool
{
$this->store = [];
return true;
}

public function getMultiple($keys, $default = null): array
{
$results = [];
foreach ($keys as $key) {
$results[$key] = $this->get($key, $default);
}
return $results;
}

public function setMultiple($values, $ttl = null): bool
{
foreach ($values as $key => $value) {
$this->set($key, $value, $ttl);
}
return true;
}

public function deleteMultiple($keys): bool
{
foreach ($keys as $key) {
$this->delete($key);
}
return true;
}

public function has($key): bool
{
return array_key_exists($key, $this->store);
}
}
Loading