keepsuit / liquid
PHP implementation of liquid markup language
Installs: 2 434
Dependents: 3
Suggesters: 0
Security: 0
Stars: 11
Watchers: 2
Forks: 1
Open Issues: 0
Requires
- php: ^8.1
- ext-mbstring: *
Requires (Dev)
- laravel/pint: ^1.2
- pestphp/pest: ^2.7
- pestphp/pest-plugin-arch: ^2.2
- phpbench/phpbench: dev-master
- phpstan/extension-installer: ^1.3
- phpstan/phpstan: ^2.0
- phpstan/phpstan-deprecation-rules: ^2.0
- spatie/invade: ^2.0
- spatie/ray: ^1.28
- symfony/console: ^6.3 || ^7.0
- symfony/yaml: ^6.0 || ^7.0
This package is auto-updated.
Last update: 2025-01-09 16:39:51 UTC
README
This is a PHP porting of the Shopify Liquid template engine.
If you are using laravel, you can use the laravel-liquid package.
Liquid is a template engine with interesting advantages:
- It is easy to learn and has a simple syntax.
- It is safe since it does not allow users to run insecure code on your server.
- It is extensible, allowing you to add your own filters and tags.
Shopify Liquid version compatibility
Differences from Shopify Liquid
- Error Modes are not implemented, the parsing is always strict.
include
tag is not implemented because it is deprecated and can be replaced withrender
.
Installation
You can install the package via composer:
composer require keepsuit/liquid
Usage
Create a new environment factory instance:
$environment = \Keepsuit\Liquid\EnvironmentFactory::new() // enable strict variables mode ->setStrictVariables() // enable strict filters mode ->setStrictFilters() // rethrow exceptions instead of rendering them ->setRethrowErrors() // replace the default error handler ->setErrorHandler(new \Keepsuit\Liquid\ErrorHandlers\DefaultErrorHandler()) // set filesystem used to load templates ->setFilesystem(new \Keepsuit\Liquid\FileSystems\LocalFileSystem(__DIR__ . '/views')) // set the resource limits ->setResourceLimits(new \Keepsuit\Liquid\ResourceLimits()) // register a custom extension ->addExtension(new CustomExtension()) // register a custom tag ->registerTag(CustomTag::class) // register a custom filters provider ->registerFilters(CustomFilters::class) // build the environment ->build();
Then create a new template instance parsing a liquid template:
/** @var \Keepsuit\Liquid\Environment $environment */ // Parse from string $template = $environment->parseString('Hello {{ name }}!'); // Parse from template (loaded from filesystem) $template = $environment->parseTemplate('index');
And finally render the template:
/** @var \Keepsuit\Liquid\Environment $environment */ /** @var \Keepsuit\Liquid\Template $template */ // Create the render context $context = $environment->newRenderContext( // Data available only in the current context data: [ 'name' => 'John', ], // Data shared with all sub-contexts staticData: [] ) $view = $template->render($context); // $view = 'Hello John!';
For advanced use cases, you can also stream the rendering output (still experimental):
/** @var \Keepsuit\Liquid\Template $template */ /** @var \Keepsuit\Liquid\Render\RenderContext $context */ $stream = $template->stream($context); // $stream is a Generator<string>
Drops
Liquid support almost any kind of object but in order to have a better control over the accessible data in the templates,
you can pass your data as Drop
objects and have a better control over the accessible data.
Drops are standard php objects that extend the Keepsuit\Liquid\Drop
class.
Public properties and public methods of the class will be accessible in the template as a property.
You can also override the liquidMethodMissing
method to handle undefined properties.
Liquid provides some attributes to control the behavior of the drops:
Hidden
: Hide the method or the property from the template, it cannot be accessed from liquid.Cache
: Cache the result of the method, it will be called only once and the result will be stored in the drop.
use Keepsuit\Liquid\Drop; class ProductDrop extends Drop { public function __construct(private Product $product) {} public function title(): string { return $this->product->title; } public function price(): float { return round($this->product->price, 2); } #[\Keepsuit\Liquid\Attributes\Cache] public function expensiveOperation(){ // complex operation } #[\Keepsuit\Liquid\Attributes\Hidden] public function buy(){ // Do something } }
If you implement the MapsToLiquid
interface in your domain classes,
the liquid renderer will automatically convert your objects to drops.
use Keepsuit\Liquid\Contracts\MapsToLiquid; class Product implements MapsToLiquid { public function __construct(public string $title, public float $price) {} public function toLiquid(): ProductDrop { return new ProductDrop($this); } }
Advanced usage
Custom tags
To create a custom tag, you need to create a class that extends the Keepsuit\Liquid\Tag
abstract class (or Keepsuit\Liquid\TagBlock
if tag has a body).
use Keepsuit\Liquid\Parse\TagParseContext; use Keepsuit\Liquid\Render\RenderContext; use Keepsuit\Liquid\Tag; class CustomTag extends Tag { public static function tagName(): string { return 'custom'; } public function render(RenderContext $context): string { return ''; } public function parse(TagParseContext $context): static { return $this; } }
Note
Take a look at the implementation of default tags to see how to implement parse
and render
methods.
Then you need to register the tag in the environment:
// register when building the environment $environment = \Keepsuit\Liquid\EnvironmentFactory::new() ->registerTag(CustomTag::class) ->build(); // or directly in the environment $environment->tagRegistry->register(CustomTag::class);
Custom filters
To create a custom filter, you need to create a class that extends the Keepsuit\Liquid\Filters\FiltersProvider
abstract class.
Each public method of the class will be registered as a filter.
You can "hide" a public method with the Hidden
attribute, so it will not be registered as filter.
use Keepsuit\Liquid\Filters\FiltersProvider; class CustomFilters extends FiltersProvider { public function customFilter(string $value): string { return 'custom '.$value; } #[\Keepsuit\Liquid\Attributes\Hidden] public function notAFilter(string $value): string { return 'hidden '.$value; } }
Then you need to register the filters provider in the environment:
// register when building the environment $environment = \Keepsuit\Liquid\EnvironmentFactory::new() ->registerFilters(CustomFilters::class) ->build(); // or directly in the environment $environment->filterRegistry->register(CustomFilters::class);
Extensions
Extensions allow you to add custom tags, filters, and other features to the liquid environment.
To create a custom extension, you need to create a class that extends the Keepsuit\Liquid\Extensions\Extension
abstract class.
class CustomExtension extends \Keepsuit\Liquid\Extensions\Extension { public function getTags() : array{ return [ CustomTag::class, ]; } public function getFiltersProviders() : array{ return [ CustomFilters::class, ]; } // custom registers passed to render context public function getRegisters() : array { return [ 'custom' => fn() => 'custom value', ]; } }
Then you need to register the extension in the environment:
// register when building the environment $environment = \Keepsuit\Liquid\EnvironmentFactory::new() ->addExtension(new CustomExtension()) ->build(); // or directly in the environment $environment->addExtension(new CustomExtension());
Custom tags and filters
By default, only the standard liquid tags and filters are available. But this package provides some custom tags and filters that you can use.
Tags
DynamicRender
: This tag replace the defaultRender
tag and allows to render dynamic templates (eg. read template name from a variable).
Filters
TernaryFilter
ternary
: adds a ternary operator.{{ condition | ternary: true_value, false_value }} # Example {{ true | ternary: 'yes', 'no' }} # yes {{ false | ternary: 'yes', 'no' }} # no
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Credits
License
The MIT License (MIT). Please see License File for more information.