Skip to content

Commit

Permalink
dox and mostly cosmetic stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
dakujem committed Aug 31, 2022
1 parent 87c2633 commit ca831bb
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 80 deletions.
80 changes: 59 additions & 21 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,40 @@ $app->run();
It would of course be an overkill to use Latte in a case like the one above.\
Most of the time, one will use Latte for more complicated templates with multiple variables, filters and macros.

In such applications, it will make sense to define a `Latter\View` service in a service container for dependency injection.
In such applications, it will make sense to define a `Latter\View` service in a service container for dependency injection (see below).


## Cofigure Latte\Engine factory service
## Render or complete a template

A template is an incomplete text with missing parts in it. The text is completed during rendering process, when the missing parts are replaced by variables (rendering parameters).

Latter `View` offers a way to complete the template and get the result as a string.\
`View::complete` serves this purpose.

```php
$view->complete(
$template, // the template
$variables // the variables
); // returns a string
```

In Latte slang, _rendering_ equals to completing a template and sending the result to the output.
By PSR-7 standard, this equals to populating a response's body with the result.\
`View::render` serves this purpose.

```php
$view->render(
$response, // PSR-7 response interface implementation
$template, // the template
$variables // the variables
); // returns a populated Response object
```

> 💡\
> These two are equivalent in terms of the completing process, they only differ in the return value and the extra parameter. This documentation uses both interchangably.

## Configure Latte\Engine factory service

First, let's create a service container.\
I'll be using [Sleeve](https://github.com/dakujem/sleeve), a trivial extension of Symfony [Pimple container](https://pimple.symfony.com/).
Expand Down Expand Up @@ -148,7 +178,7 @@ $view->alias('hello', 'hello.latte');
$view->alias('index', 'ClientModule/Index/default.latte');
```

To render a template using its alias:
To complete/render a template using its alias:
```php
$view->render($response, 'hello', $params);
$view->render($response, 'index', $params);
Expand All @@ -159,18 +189,19 @@ $view->render($response, 'index', $params);

Render routines should be used to apply template-specific setup without the need for code repetition.

They may be used to
- define filters
- define tags (macros)
- modify input parameters
- modify template name
- or even to use a completely different Engine instance or render own Response

A render routine is a _callable_ that receives a [`Runtime`](src/Runtime.php) context object and returns a _response_, with the following signature:
A render routine is a _callable_ that receives a [`Runtime`](src/Runtime.php) context object and either returns a `Runtime` object or a `string`; signature:
```
function(Dakujem\Latter\Runtime $context): Psr\Http\Message\ResponseInterface | Dakujem\Latter\Runtime
function(Dakujem\Latter\Runtime $context): Dakujem\Latter\Runtime | string
```

Returning a `Runtime` object by a routine may be used
- to define filters (this will be the most common case)
- to modify input parameters
- to modify template name
- to define tags (macros)

When a routine returns a `string`, it is considered the result of the rendering process and is not processed any further.

Example:
```php
$view->register('shopping-cart', function (Runtime $context) {
Expand All @@ -191,13 +222,18 @@ $view->register('shopping-cart', function (Runtime $context) {
// The params can be modified at will, for example to provide defaults
$params = array_merge(['default' => 'value'], $context->getParams());

// the Runtime::toResponse helper method can be used for default rendering
// a Runtime object is returned
return $context->withTarget($template)->withParams($params);
});
$view->register('hello', function () {
// return a static text, bypassing any further rendering process
return 'Hello world.';
});
```

One can render the routine exactly as he would render an alias:
One can complete/render a template using a routine exactly as he would do with an alias:
```php
$view->complete('shopping-cart', $params);
$view->render($response, 'shopping-cart', $params);
```

Expand Down Expand Up @@ -257,7 +293,7 @@ $view->register('--withUser--', function (Runtime $context) {
});
```

For pre-render routines used in pipelines, it is important to return a `Runtime` context object. If a `Response` was returned, the pipeline would end prematurely (this might be desired in certain cases though). Return value of any other kind is ignored.
For pre-render routines used in pipelines, it is important to return a `Runtime` context object. If a `string` was returned, the pipeline would end prematurely (this might be desired in certain cases though). Return value of any other kind is ignored.

Render calls using pipelines could look like these:
```php
Expand All @@ -266,10 +302,10 @@ $view
->pipeline('base-layout', '--withUser--')
->render($response, 'shopping-cart', $params);

// rendering a file with a common _pre-render_ routine
// completing a file with a common _pre-render_ routine
$view
->pipeline('--withUser--')
->render($response, 'userProfile.latte', $params);
->complete('userProfile.latte', $params);
```

Pipelines are particularly useful when dealing with included templates (header, footer) or layout templates that require specific variables or filters to render.\
Expand All @@ -294,12 +330,13 @@ $view->pipeline('base-layout')->render($response, 'about.latte');

This kind of rendering could be compared to tagging or decorating templates before rendering.

Alternatively, it is also possible to define the pipeline as a part of the rendering routine:
Alternatively, it is also possible to define the pipeline as an inseparable part of the rendering routine:
```php
$view->register('contacts.latte', $view->pipeline('base-layout', function (Runtime $context) {
$pipeline = $view->pipeline('base-layout', function (Runtime $context) {
// ... do whatever setup needed for rendering the contacts page
return $context->withParams(['foo' => 'bar']);
}));
});
$view->register('contacts.latte', $pipeline);
```
```latte
{*} contacts.latte {*}
Expand All @@ -309,6 +346,7 @@ $view->register('contacts.latte', $view->pipeline('base-layout', function (Runti
```
```php
$view->render($response, 'contacts.latte');
$view->complete('contacts.latte');
```


Expand Down Expand Up @@ -339,7 +377,7 @@ Latte templates are compiled on first render. All subsequent renders will use co

To slightly improve performance on production servers, auto-refresh can be turned off in the Engine factory:\
`$engine->setAutoRefresh($container->settings['dev'] ?? true);`\
This has its caveats, read the Latte docs beforehand.
This has its caveats if the compiled code is not purged during deployment process though.


### Use {link} and n:href macros with Slim framework
Expand Down
8 changes: 4 additions & 4 deletions src/PipelineRelay.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@ final class PipelineRelay implements Renderer
/** @var callable|null */
private $agent;
/** @var callable */
private $renderHandler;
private $renderer;

public function __construct(
array $routines,
callable $executor,
callable $agent,
callable $renderHandler = null
callable $renderer = null
)
{
$this->routines = $routines;
$this->executor = $executor;
$this->agent = $agent;
$this->renderHandler = $renderHandler;
$this->renderer = $renderer;
}

public function __invoke(...$args)
Expand All @@ -59,6 +59,6 @@ public function complete(string $target, array $params = [], Engine $latte = nul

public function render(Response $response, string $target, array $params = [], Engine $latte = null, ...$args): Response
{
return ($this->renderHandler)($this->routines, $response, $target, $params, $latte, ...$args);
return ($this->renderer)($this->routines, $response, $target, $params, $latte, ...$args);
}
}
76 changes: 21 additions & 55 deletions src/View.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,6 @@ class View implements Renderer
*/
public function complete(string $target, array $params = [], Engine $latte = null): string
{

// TODO this could be used in general, for any stacks.
// there is a class of resulting problems though - like terminating a pipeline by returning a Response (which might itself be obsolete),
// but the response would not be in the Runtime object. It could be solved by returning a (new) Result object.
//
// TODO interface, Runtime

// get the rendering routine, fall back to the default one
$routine = $this->getRoutine($target) ?? $this->getDefaultRoutine();
// create a starting context
Expand All @@ -73,52 +66,6 @@ public function render(Response $response, string $target, array $params = [], E
return $this->populateResponse($response, $content);
}

/**
* Render a given template to into a response body.
* The actual Latte rendering occurs within.
*
* @param Response $response
* @param Engine $latte
* @param string $template
* @param array $params
* @return Response
*/
public function respond(Response $response, Engine $latte, string $template, array $params): Response
{
$content = $latte->renderToString($template, array_merge($this->getDefaultParams(), $params));
$response->getBody()->write($content);
return $response;
}

/**
* Render a given template to string.
* The actual Latte rendering process occurs within.
*
* @param Engine $latte
* @param string $template
* @param array $params
* @return string
*/
private function renderLatteTemplate(Engine $latte, string $template, array $params = []): string
{
return $latte->renderToString($template, array_merge($this->getDefaultParams(), $params));
}


/**
* Write given content to a Response object's body.
*
* @param Response $response
* @param string $content
* @return Response
*/
private function populateResponse(Response $response, string $content): Response
{
$response->getBody()->write($content);
return $response;
}


/**
* Create a rendering pipeline from registered routine names or callable routines.
*
Expand Down Expand Up @@ -162,7 +109,7 @@ public function pipeline(...$routines): PipelineRelay
return $executor($context, $routines);
};
$renderer = function (array $routines, Response $response, string $target, array $params = [], Engine $latte = null) use ($agent) {
$content = call_user_func($agent, $routines, $target, $params, $latte);
$content = call_user_func($agent, $routines, $target, $params, $latte);
return $this->populateResponse($response, $content);
};
return new PipelineRelay($queue, $executor, $agent, $renderer);
Expand Down Expand Up @@ -358,6 +305,21 @@ public function getEngine(): ?Engine
# || Internal ||
# ++------------++


/**
* Write given content to a Response object's body.
*
* @param Response $response
* @param string $content
* @return Response
*/
protected function populateResponse(Response $response, string $content): Response
{
$response->getBody()->write($content);
return $response;
}


/**
* Terminate rendering using a context and a routine, if provided.
*
Expand Down Expand Up @@ -394,7 +356,11 @@ protected function terminal(): callable
if ($context->getEngine() === null) {
throw new LogicException('Engine is needed.');
}
return $this->renderLatteTemplate($context->getEngine(), $context->getTarget(), $context->getParams());
$engine = $context->getEngine();
return $engine->renderToString(
$context->getTarget(),
array_merge($this->getDefaultParams(), $context->getParams())
);
};
}
}
16 changes: 16 additions & 0 deletions tests/routines.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ class RoutinesTest extends BaseTest
$this->assert('Hugo has got oranges.', 'has.latte', ['name' => 'Hugo', 'object' => 'oranges'], $v);
}

public function testStaticRoutine()
{
$v = $this->view();

// missing template file
Assert::exception(function () use ($v) {
$v->complete('hello');
}, RuntimeException::class);

// renders correctly
$v->register('hello', function () {
return 'Hello my worlds.';
});
Assert::same('Hello my worlds.', $v->complete('hello'));
}

public function testDefaultParameters()
{
$v = $this->view();
Expand Down

0 comments on commit ca831bb

Please sign in to comment.