Skip to content

Repository files navigation

PHP implementation of Liquid markup language

Latest Version on PackagistTestsTotal Downloads

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

PHP LiquidShopify Liquid
v0.11v5.13
v0.10v5.12
v0.9v5.8
v0.8v5.7
v0.7v5.6
v0.1 - v0.6v5.5

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 with render.

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 (disabled by default)
->setStrictVariables(true)
// enable strict filters mode (disabled by default)
->setStrictFilters(true)
// rethrow exceptions instead of rendering them (disabled by default)
->setRethrowErrors(true)
// disable lazy parsing (enabled by default)
->setLazyParsing(false)
// 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\Render\ResourceLimits(
renderLengthLimit: 100_000,
renderScoreLimit: 50_000,
assignScoreLimit: 5_000,
cumulativeRenderScoreLimit: 100_000,
cumulativeAssignScoreLimit: 10_000,
))
// register a custom extension
->addExtension(newCustomExtension())
// 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.
useKeepsuit\Liquid\Drop;
class ProductDrop extends Drop {
publicfunction__construct(privateProduct$product) {}
publicfunctiontitle(): string {
return$this->product->title;
}
publicfunctionprice(): float {
returnround($this->product->price, 2);
}
#[\Keepsuit\Liquid\Attributes\Cache]
publicfunctionexpensiveOperation(){
// complex operation
}
#[\Keepsuit\Liquid\Attributes\Hidden]
publicfunctionbuy(){
// Do something
}
}

If you implement the MapsToLiquid interface in your domain classes, the liquid renderer will automatically convert your objects to drops.

useKeepsuit\Liquid\Contracts\MapsToLiquid;
class Product implements MapsToLiquid {
publicfunction__construct(publicstring$title, publicfloat$price) {}
publicfunctiontoLiquid(): ProductDrop {
returnnewProductDrop($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).

useKeepsuit\Liquid\Parse\TagParseContext;
useKeepsuit\Liquid\Render\RenderContext;
useKeepsuit\Liquid\Tag;
class CustomTag extends Tag
{
publicstaticfunctiontagName(): string
{
return'custom';
}
publicfunctionrender(RenderContext$context): string
{
return'';
}
publicfunctionparse(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.

useKeepsuit\Liquid\Filters\FiltersProvider;
class CustomFilters extends FiltersProvider
{
publicfunctioncustomFilter(string$value): string
{
return'custom '.$value;
}
#[\Keepsuit\Liquid\Attributes\Hidden]
publicfunctionnotAFilter(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
{
publicfunctiongetTags() : array{
return [
CustomTag::class,
];
}
publicfunctiongetFiltersProviders() : array{
return [
CustomFilters::class,
];
}
// custom registers passed to render contextpublicfunctiongetRegisters() : 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(newCustomExtension())
->build();
// or directly in the environment$environment->addExtension(newCustomExtension());

Resource limits

Keepsuit\Liquid\Render\ResourceLimits supports both per-render limits and cumulative resource limits.

  • renderLengthLimit: limits the rendered output size for a render pass.
  • renderScoreLimit: limits render work for a render pass.
  • assignScoreLimit: limits assignment and capture work for a render pass.
  • cumulativeRenderScoreLimit: limits total render work across a full render tree, including partial renders.
  • cumulativeAssignScoreLimit: limits total assignment and capture work across a full render tree, including partial renders.

Per-render counters can be reset between renders. Cumulative counters are intended for a full render lifecycle so repeated partial renders can share the same budget.

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 default Render 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.

About

PHP implementation of Liquid markup language

Resources

Stars

19 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages