Skip to content
Closed
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
16 changes: 7 additions & 9 deletions src/Parse/ParseContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,18 @@ class ParseContext

protected bool $partial = false;

protected PartialsCache $partialsCache;

protected OutputsBag $outputs;

protected Lexer $lexer;

protected Parser $parser;

public function __construct(
public readonly bool $allowDynamicPartials = false,
public readonly TagRegistry $tagRegistry = new TagRegistry(),
public readonly LiquidFileSystem $fileSystem = new BlankFileSystem(),
protected PartialsCache $partialsCache = new PartialsCache(),
protected OutputsBag $outputs = new OutputsBag(),
) {
$this->lineNumber = 1;
$this->outputs = new OutputsBag();
$this->partialsCache = new PartialsCache();
$this->lexer = new Lexer($this);
$this->parser = new Parser($this);
}
Expand Down Expand Up @@ -72,16 +69,17 @@ public function loadPartial(string $templateName): Template
}

$partialParseContext = new ParseContext(
allowDynamicPartials: $this->allowDynamicPartials,
tagRegistry: $this->tagRegistry,
fileSystem: $this->fileSystem,
partialsCache: $this->partialsCache,
outputs: $this->outputs,
);
$partialParseContext->partial = true;
$partialParseContext->depth = $this->depth;
$partialParseContext->outputs = $this->outputs;
$partialParseContext->partialsCache = $this->partialsCache;

try {
$source = $this->fileSystem->readTemplateFile($templateName);
$source = $partialParseContext->fileSystem->readTemplateFile($templateName);

$template = Template::parse($partialParseContext, $source, $templateName);

Expand Down
8 changes: 2 additions & 6 deletions src/Render/ContextSharedState.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,11 @@
namespace Keepsuit\Liquid\Render;

use Keepsuit\Liquid\Support\OutputsBag;
use Keepsuit\Liquid\Template;
use Keepsuit\Liquid\Support\PartialsCache;
use WeakMap;

class ContextSharedState
{
/**
* @var array<string, Template>
*/
public array $partialsCache = [];

/**
* @var WeakMap<object, mixed>
*/
Expand All @@ -27,6 +22,7 @@ public function __construct(
public array $errors = [],
/** @var array<string, int> */
public array $disabledTags = [],
public PartialsCache $partialsCache = new PartialsCache(),
public OutputsBag $outputs = new OutputsBag(),
) {
$this->computedObjectsCache = new WeakMap();
Expand Down
30 changes: 23 additions & 7 deletions src/Render/RenderContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
use Keepsuit\Liquid\Support\FilterRegistry;
use Keepsuit\Liquid\Support\MissingValue;
use Keepsuit\Liquid\Support\OutputsBag;
use Keepsuit\Liquid\Support\PartialsCache;
use Keepsuit\Liquid\Support\TagRegistry;
use Keepsuit\Liquid\Template;
use RuntimeException;
use Throwable;
Expand Down Expand Up @@ -79,10 +81,12 @@ public function __construct(
array $registers = [],
public readonly bool $rethrowExceptions = false,
public readonly bool $strictVariables = false,
public readonly bool $allowDynamicPartials = false,
bool $profile = false,
public readonly FilterRegistry $filterRegistry = new FilterRegistry(),
public readonly ResourceLimits $resourceLimits = new ResourceLimits(),
public readonly LiquidFileSystem $fileSystem = new BlankFileSystem(),
public readonly TagRegistry $tagRegistry = new TagRegistry(),
) {
$this->scopes = [[]];

Expand Down Expand Up @@ -320,28 +324,40 @@ public function getProfiler(): ?Profiler

public function loadPartial(string $templateName): Template
{
if (! Arr::has($this->sharedState->partialsCache, $templateName)) {
throw new StandardException(sprintf("The partial '%s' has not be loaded during parsing", $templateName));
if ($template = $this->sharedState->partialsCache->get($templateName)) {
return $template;
}

return $this->sharedState->partialsCache[$templateName];
if (! $this->allowDynamicPartials) {
throw new StandardException('Dynamic templates are not allowed');
}

$parseContext = new ParseContext(
allowDynamicPartials: $this->allowDynamicPartials,
tagRegistry: $this->tagRegistry,
fileSystem: $this->fileSystem,
partialsCache: $this->sharedState->partialsCache,
outputs: $this->sharedState->outputs,
);

return $parseContext->loadPartial($templateName);
}

public function setPartialsCache(array $partialsCache): RenderContext
public function setPartialsCache(PartialsCache $partialsCache): RenderContext
{
$this->sharedState->partialsCache = $partialsCache;

return $this;
}

public function mergePartialsCache(array $partialsCache): RenderContext
public function mergePartialsCache(PartialsCache $partialsCache): RenderContext
{
$this->sharedState->partialsCache = array_merge($this->sharedState->partialsCache, $partialsCache);
$this->sharedState->partialsCache->merge($partialsCache);

return $this;
}

public function mergeOutputs(array $outputs): RenderContext
public function mergeOutputs(OutputsBag $outputs): RenderContext
{
$this->sharedState->outputs->merge($outputs);

Expand Down
4 changes: 2 additions & 2 deletions src/Support/OutputsBag.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ public function all(): array
return $this->bags;
}

public function merge(array $outputs): void
public function merge(OutputsBag $outputs): void
{
foreach ($outputs as $key => $value) {
foreach ($outputs->all() as $key => $value) {
$this->bags[$key] = $value;
}
}
Expand Down
24 changes: 19 additions & 5 deletions src/Tags/RenderTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
*/
class RenderTag extends Tag implements CanBeStreamed, HasParseTreeVisitorChildren
{
protected string $templateNameExpression;
protected string|VariableLookup $templateNameExpression;

protected mixed $variableNameExpression;

Expand All @@ -48,6 +48,9 @@ public function parse(TagParseContext $context): static
$templateNameExpression = $context->params->expression();
$this->templateNameExpression = match (true) {
is_string($templateNameExpression) => $templateNameExpression,
$templateNameExpression instanceof VariableLookup => $context->getParseContext()->allowDynamicPartials
? $templateNameExpression
: throw new SyntaxException('Dynamic partials are not allowed'),
default => throw new SyntaxException('Template name must be a string'),
};

Expand Down Expand Up @@ -80,7 +83,9 @@ public function parse(TagParseContext $context): static

$context->params->assertEnd();

$context->getParseContext()->loadPartial($this->templateNameExpression);
if (is_string($this->templateNameExpression)) {
$context->getParseContext()->loadPartial($this->templateNameExpression);
}
});

return $this;
Expand All @@ -99,9 +104,18 @@ public function render(RenderContext $context): string

public function stream(RenderContext $context): \Generator
{
$partial = $context->loadPartial($this->templateNameExpression);
$templateName = match (true) {
is_string($this->templateNameExpression) => $this->templateNameExpression,
$this->templateNameExpression instanceof VariableLookup => $this->templateNameExpression->evaluate($context),
};

if (! is_string($templateName)) {
throw new SyntaxException('Template name must be a string');
}

$partial = $context->loadPartial($templateName);

$contextVariableName = $this->aliasName ?? Arr::last(explode('/', $this->templateNameExpression));
$contextVariableName = $this->aliasName ?? Arr::last(explode('/', $templateName));
assert(is_string($contextVariableName));

$variable = $this->variableNameExpression ? $context->evaluate($this->variableNameExpression) : null;
Expand All @@ -110,7 +124,7 @@ public function stream(RenderContext $context): \Generator
$variable = $variable instanceof Traversable ? iterator_to_array($variable) : $variable;
assert(is_array($variable));

$forLoop = new ForLoopDrop($this->templateNameExpression, count($variable));
$forLoop = new ForLoopDrop($templateName, count($variable));

foreach ($variable as $value) {
$partialContext = $this->buildPartialContext($partial, $context, [
Expand Down
8 changes: 4 additions & 4 deletions src/Template.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ public static function parse(ParseContext $parseContext, string $source, ?string
);

if (! $parseContext->isPartial()) {
$template->state->partialsCache = $parseContext->getPartialsCache()->all();
$template->state->outputs = $parseContext->getOutputs()->all();
$template->state->partialsCache = $parseContext->getPartialsCache();
$template->state->outputs = $parseContext->getOutputs();
}

return $template;
Expand Down Expand Up @@ -70,7 +70,7 @@ public function render(RenderContext $context): string
throw $e;
} finally {
$this->state->errors = $context->getErrors();
$this->state->outputs = $context->getOutputs()->all();
$this->state->outputs = $context->getOutputs();
}
}

Expand All @@ -91,7 +91,7 @@ public function stream(RenderContext $context): \Generator
throw $e;
} finally {
$this->state->errors = $context->getErrors();
$this->state->outputs = $context->getOutputs()->all();
$this->state->outputs = $context->getOutputs();
}
}

Expand Down
17 changes: 17 additions & 0 deletions src/TemplateFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ final class TemplateFactory

protected bool $strictVariables = false;

protected bool $allowDynamicPartials = false;

public function __construct()
{
$this->tagRegistry = $this->buildTagRegistry();
Expand Down Expand Up @@ -111,6 +113,18 @@ public function getStrictVariables(): bool
return $this->strictVariables;
}

public function setAllowDynamicPartials(bool $allowDynamicPartials = true): TemplateFactory
{
$this->allowDynamicPartials = $allowDynamicPartials;

return $this;
}

public function getAllowDynamicPartials(): bool
{
return $this->allowDynamicPartials;
}

/**
* Enable/disabled rethrowExceptions and strictVariables.
*/
Expand All @@ -125,6 +139,7 @@ public function setDebugMode(bool $debugMode = true): TemplateFactory
public function newParseContext(): ParseContext
{
return new ParseContext(
allowDynamicPartials: $this->allowDynamicPartials,
tagRegistry: $this->tagRegistry,
fileSystem: $this->fileSystem,
);
Expand Down Expand Up @@ -158,10 +173,12 @@ public function newRenderContext(
registers: $registers,
rethrowExceptions: $this->rethrowExceptions,
strictVariables: $this->strictVariables,
allowDynamicPartials: $this->allowDynamicPartials,
profile: $this->profile,
filterRegistry: $this->filterRegistry,
resourceLimits: $this->resourceLimits,
fileSystem: $this->fileSystem,
tagRegistry: $this->tagRegistry,
);
}

Expand Down
26 changes: 12 additions & 14 deletions src/TemplateSharedState.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@

namespace Keepsuit\Liquid;

use Keepsuit\Liquid\Support\OutputsBag;
use Keepsuit\Liquid\Support\PartialsCache;

class TemplateSharedState
{
/**
* @var array<\Throwable>
*/
public array $errors = [];

/**
* @var array<string,Template>
*/
public array $partialsCache = [];

/**
* @var array<string,mixed>
*/
public array $outputs = [];
public function __construct(
/**
* @var array<\Throwable>
*/
public array $errors = [],
public PartialsCache $partialsCache = new PartialsCache(),
public OutputsBag $outputs = new OutputsBag(),
) {
}
}
5 changes: 5 additions & 0 deletions tests/Integration/Tags/RenderTagTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@
->toThrow(SyntaxException::class);
});

test('dynamically template name', function () {
expect(renderTemplate("{% assign name = 'snippet' %}{% render name %}", partials: ['snippet' => 'echo'], allowDynamicTemplates: true))
->toBe('echo');
});

test('render tag caches second read of some partial', function () {
$factory = TemplateFactory::new()
->setFilesystem($fileSystem = new StubFileSystem(['snippet' => 'echo']));
Expand Down
4 changes: 3 additions & 1 deletion tests/Pest.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ function renderTemplate(
array $partials = [],
bool $renderErrors = false,
bool $strictVariables = false,
bool $allowDynamicTemplates = false,
TemplateFactory $factory = new TemplateFactory()
): string {
$factory = $factory
->setFilesystem(new StubFileSystem(partials: $partials))
->setRethrowExceptions(! $renderErrors)
->setStrictVariables($strictVariables);
->setStrictVariables($strictVariables)
->setAllowDynamicPartials($allowDynamicTemplates);

$template = $factory->parseString($template);

Expand Down