Skip to content

Commit

Permalink
Merge pull request #11 from keepsuit/stream
Browse files Browse the repository at this point in the history
Streaming
  • Loading branch information
cappuc authored Jan 31, 2024
2 parents cd76e08 + 31cea75 commit 1d751fd
Show file tree
Hide file tree
Showing 12 changed files with 359 additions and 28 deletions.
16 changes: 16 additions & 0 deletions performance/CompiledThemeTestTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@ public function render(array $assigns = []): void
}
}

public function stream(array $assigns = []): void
{
$content = $this->template->stream($this->buildContext($assigns));

if ($this->layout) {
$content = $this->layout->stream($this->buildContext([
...$assigns,
'content_for_layout' => $content,
]));
}

while ($content->valid()) {
$content->next();
}
}

protected function buildContext(array $assigns = []): RenderContext
{
return $this->factory->newRenderContext(
Expand Down
23 changes: 22 additions & 1 deletion performance/ThemeRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,32 @@ public function render(): void
}
}

public function stream(): void
{
$database = [...Database::tables()];

foreach ($this->compiledTemplates as $compiled) {
$compiled->stream($database);
}
}

public function run(): void
{
$database = [...Database::tables()];

foreach ($this->tests as $test) {
$compiled = $test->compile();
$compiled->render($database);
}
}

public function runStreaming(): void
{
$database = [...Database::tables()];

foreach ($this->tests as $test) {
$compiled = $test->compile();
$compiled->render();
$compiled->stream($database);
}
}

Expand Down
10 changes: 10 additions & 0 deletions performance/benchmarks/LiquidBench.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,21 @@ public function benchRender(): void
$this->themeRunner->render();
}

public function benchStream(): void
{
$this->themeRunner->stream();
}

public function benchParsingAndRendering(): void
{
$this->themeRunner->run();
}

public function benchParsingAndStreaming(): void
{
$this->themeRunner->runStreaming();
}

protected function getThemeRunner(): ThemeRunner
{
$templateFactory = TemplateFactory::new()
Expand Down
13 changes: 13 additions & 0 deletions src/Contracts/CanBeStreamed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Keepsuit\Liquid\Contracts;

use Keepsuit\Liquid\Render\RenderContext;

interface CanBeStreamed extends CanBeRendered
{
/**
* @return \Generator<string>
*/
public function stream(RenderContext $context): \Generator;
}
51 changes: 50 additions & 1 deletion src/Nodes/BodyNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

namespace Keepsuit\Liquid\Nodes;

use Keepsuit\Liquid\Contracts\CanBeStreamed;
use Keepsuit\Liquid\Exceptions\LiquidException;
use Keepsuit\Liquid\Exceptions\UndefinedDropMethodException;
use Keepsuit\Liquid\Exceptions\UndefinedFilterException;
use Keepsuit\Liquid\Exceptions\UndefinedVariableException;
use Keepsuit\Liquid\Render\RenderContext;
use Keepsuit\Liquid\Tag;

class BodyNode extends Node
class BodyNode extends Node implements CanBeStreamed
{
public function __construct(
/** @var array<Node> */
Expand Down Expand Up @@ -74,6 +75,34 @@ public function render(RenderContext $context): string
return $output;
}

public function stream(RenderContext $context): \Generator
{
$context->resourceLimits->incrementRenderScore(count($this->children));

foreach ($this->children as $node) {
try {
if ($node instanceof Tag) {
$node->ensureTagIsEnabled($context);
}

foreach ($this->streamChild($context, $node) as $output) {
$context->resourceLimits->incrementWriteScore($output);
yield $output;
}
} catch (UndefinedVariableException|UndefinedDropMethodException|UndefinedFilterException $exception) {
$context->handleError($exception, $node->lineNumber);
} catch (\Throwable $exception) {
$output = $context->handleError($exception, $node->lineNumber);
$context->resourceLimits->incrementWriteScore($output);
yield $output;
}

if ($context->hasInterrupt()) {
break;
}
}
}

protected function renderChild(RenderContext $context, Node $node): string
{
if ($context->getProfiler() !== null) {
Expand All @@ -87,6 +116,26 @@ protected function renderChild(RenderContext $context, Node $node): string
return $node->render($context);
}

/**
* @return \Generator<string>
*/
public function streamChild(RenderContext $context, Node $node): \Generator
{
if ($context->getProfiler() !== null) {
yield $this->renderChild($context, $node);

return;
}

if ($node instanceof CanBeStreamed) {
yield from $node->stream($context);

return;
}

yield $node->render($context);
}

public function blank(): bool
{
foreach ($this->children as $node) {
Expand Down
17 changes: 16 additions & 1 deletion src/Nodes/Document.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
namespace Keepsuit\Liquid\Nodes;

use Keepsuit\Liquid\Contracts\CanBeRendered;
use Keepsuit\Liquid\Contracts\CanBeStreamed;
use Keepsuit\Liquid\Exceptions\LiquidException;
use Keepsuit\Liquid\Render\RenderContext;

class Document implements CanBeRendered
class Document implements CanBeRendered, CanBeStreamed
{
public function __construct(
protected BodyNode $body,
Expand All @@ -29,6 +30,20 @@ public function render(RenderContext $context): string
return $this->body->render($context);
}

/**
* @throws LiquidException
*/
public function stream(RenderContext $context): \Generator
{
if ($context->getProfiler() !== null) {
yield $this->render($context);

return;
}

yield from $this->body->stream($context);
}

/**
* @return array<Node>
*/
Expand Down
77 changes: 64 additions & 13 deletions src/Nodes/Variable.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Keepsuit\Liquid\Contracts\CanBeEvaluated;
use Keepsuit\Liquid\Contracts\CanBeRendered;
use Keepsuit\Liquid\Contracts\CanBeStreamed;
use Keepsuit\Liquid\Contracts\HasParseTreeVisitorChildren;
use Keepsuit\Liquid\Parse\ExpressionParser;
use Keepsuit\Liquid\Render\RenderContext;
Expand All @@ -12,7 +13,7 @@
/**
* @phpstan-import-type Expression from ExpressionParser
*/
class Variable extends Node implements CanBeEvaluated, HasParseTreeVisitorChildren
class Variable extends Node implements CanBeEvaluated, CanBeStreamed, HasParseTreeVisitorChildren
{
public function __construct(
/** @var Expression $name */
Expand All @@ -30,27 +31,40 @@ public function render(RenderContext $context): string
return $output->render($context);
}

if (is_array($output)) {
return implode('', $output);
}
return $this->renderOutput($output);
}

if ($output === null) {
return '';
public function stream(RenderContext $context): \Generator
{
if ($this->filters !== []) {
yield $this->render($context);

return;
}

if (is_bool($output)) {
return $output ? 'true' : 'false';
$output = $this->evaluate($context);

if ($output instanceof CanBeStreamed) {
yield from $output->stream($context);

return;
}

if (is_string($output) || is_numeric($output)) {
return (string) $output;
if ($output instanceof CanBeRendered) {
yield $output->render($context);

return;
}

if (is_object($output) && method_exists($output, '__toString')) {
return (string) $output;
if ($output instanceof \Generator) {
foreach ($output as $chunk) {
yield $this->renderOutput($chunk);
}

return;
}

return '';
yield $this->renderOutput($output);
}

public function parseTreeVisitorChildren(): array
Expand All @@ -62,6 +76,10 @@ public function evaluate(RenderContext $context): mixed
{
$output = $context->evaluate($this->name);

if ($this->filters === []) {
return $output;
}

if ($output instanceof \Generator) {
$output = iterator_to_array($output);
}
Expand All @@ -75,6 +93,39 @@ public function evaluate(RenderContext $context): mixed
return $output;
}

protected function renderOutput(mixed $output): string
{
if (is_string($output)) {
return $output;
}

if ($output === null) {
return '';
}

if (is_bool($output)) {
return $output ? 'true' : 'false';
}

if (is_numeric($output)) {
return (string) $output;
}

if ($output instanceof \Generator) {
$output = iterator_to_array($output);
}

if (is_array($output)) {
return implode('', $output);
}

if (is_object($output) && method_exists($output, '__toString')) {
return (string) $output;
}

return '';
}

protected static function evaluateFilterExpressions(RenderContext $context, array $filterArgs): array
{
return array_map(
Expand Down
21 changes: 16 additions & 5 deletions src/Tags/RenderTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Keepsuit\Liquid\Tags;

use Keepsuit\Liquid\Contracts\CanBeStreamed;
use Keepsuit\Liquid\Contracts\HasParseTreeVisitorChildren;
use Keepsuit\Liquid\Drops\ForLoopDrop;
use Keepsuit\Liquid\Exceptions\SyntaxException;
Expand All @@ -18,7 +19,7 @@
/**
* @phpstan-import-type Expression from ExpressionParser
*/
class RenderTag extends Tag implements HasParseTreeVisitorChildren
class RenderTag extends Tag implements CanBeStreamed, HasParseTreeVisitorChildren
{
protected string $templateNameExpression;

Expand Down Expand Up @@ -86,6 +87,17 @@ public function parse(TagParseContext $context): static
}

public function render(RenderContext $context): string
{
$output = '';

foreach ($this->stream($context) as $chunk) {
$output .= $chunk;
}

return $output;
}

public function stream(RenderContext $context): \Generator
{
$partial = $context->loadPartial($this->templateNameExpression);

Expand All @@ -100,26 +112,25 @@ public function render(RenderContext $context): string

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

$output = '';
foreach ($variable as $value) {
$partialContext = $this->buildPartialContext($partial, $context, [
'forloop' => $forLoop,
$contextVariableName => $value,
]);

$output .= $partial->render($partialContext);
yield from $partial->stream($partialContext);

$forLoop->increment();
}

return $output;
return;
}

$partialContext = $this->buildPartialContext($partial, $context, [
$contextVariableName => $variable,
]);

return $partial->render($partialContext);
yield from $partial->stream($partialContext);
}

public function parseTreeVisitorChildren(): array
Expand Down
Loading

0 comments on commit 1d751fd

Please sign in to comment.