Skip to content

Commit

Permalink
Latte renderer + Nette integration
Browse files Browse the repository at this point in the history
- added renderer bridge `LatteRendererBridge`
- added default template for all types of positions in Latte
- added integration of renderers into the `AmpClientExtension`
- added tests
  • Loading branch information
tg666 committed Nov 23, 2023
1 parent 254970a commit 7226b67
Show file tree
Hide file tree
Showing 37 changed files with 1,008 additions and 128 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"kubawerlos/php-cs-fixer-custom-fixers": "^3.14",
"latte/latte": "^2.11",
"mockery/mockery": "^1.6",
"nette/application": "^3.0.8",
"nette/bootstrap": "^3.1",
"nette/caching": "^3.1",
"nette/di": "^3.0",
Expand Down
113 changes: 109 additions & 4 deletions src/Bridge/Nette/DI/AmpClientExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,56 @@

namespace SixtyEightPublishers\AmpClient\Bridge\Nette\DI;

use Closure;
use Nette\Bridges\ApplicationDI\LatteExtension;
use Nette\Bridges\ApplicationLatte\LatteFactory;
use Nette\DI\CompilerExtension;
use Nette\DI\Definitions\Reference;
use Nette\DI\Definitions\ServiceDefinition;
use Nette\DI\Definitions\Statement;
use Nette\Schema\Expect;
use Nette\Schema\Schema;
use RuntimeException;
use SixtyEightPublishers\AmpClient\AmpClient;
use SixtyEightPublishers\AmpClient\AmpClientInterface;
use SixtyEightPublishers\AmpClient\Bridge\Nette\DI\Config\AmpClientConfig;
use SixtyEightPublishers\AmpClient\Bridge\Nette\DI\Config\CacheConfig;
use SixtyEightPublishers\AmpClient\Bridge\Nette\DI\Config\HttpConfig;
use SixtyEightPublishers\AmpClient\Bridge\Nette\DI\Config\RendererConfig;
use SixtyEightPublishers\AmpClient\Bridge\Nette\NetteCacheStorage;
use SixtyEightPublishers\AmpClient\ClientConfig;
use SixtyEightPublishers\AmpClient\Http\Cache\CacheStorageInterface;
use SixtyEightPublishers\AmpClient\Http\Cache\NoCacheStorage;
use SixtyEightPublishers\AmpClient\Http\HttpClientFactory;
use SixtyEightPublishers\AmpClient\Http\HttpClientFactoryInterface;
use SixtyEightPublishers\AmpClient\Renderer\BannersResolver;
use SixtyEightPublishers\AmpClient\Renderer\BannersResolverInterface;
use SixtyEightPublishers\AmpClient\Renderer\Latte\ClosureLatteFactory;
use SixtyEightPublishers\AmpClient\Renderer\Latte\LatteRendererBridge;
use SixtyEightPublishers\AmpClient\Renderer\Phtml\PhtmlRendererBridge;
use SixtyEightPublishers\AmpClient\Renderer\Renderer;
use SixtyEightPublishers\AmpClient\Renderer\RendererBridgeInterface;
use SixtyEightPublishers\AmpClient\Renderer\RendererInterface;
use SixtyEightPublishers\AmpClient\Renderer\Templates;
use SixtyEightPublishers\AmpClient\Request\ValueObject\BannerResource;
use SixtyEightPublishers\AmpClient\Response\Hydrator\BannersResponseHydratorHandler;
use SixtyEightPublishers\AmpClient\Response\Hydrator\ResponseHydrator;
use SixtyEightPublishers\AmpClient\Response\Hydrator\ResponseHydratorHandlerInterface;
use SixtyEightPublishers\AmpClient\Response\Hydrator\ResponseHydratorInterface;
use function array_filter;
use function array_values;
use function assert;
use function count;
use function is_string;
use function sprintf;

final class AmpClientExtension extends CompilerExtension
{
private const RendererBridges = [
'phtml' => PhtmlRendererBridge::class,
'latte' => LatteRendererBridge::class,
];

public function getConfigSchema(): Schema
{
return Expect::structure([
Expand Down Expand Up @@ -67,6 +90,15 @@ public function getConfigSchema(): Schema
'http' => Expect::structure([
'guzzle_config' => Expect::array(),
])->castTo(HttpConfig::class),
'renderer' => Expect::structure([
'bridge' => Expect::anyOf(Expect::string(), Expect::type(Statement::class)),
'templates' => Expect::structure([
'single' => Expect::string(),
'random' => Expect::string(),
'multiple' => Expect::string(),
'not_found' => Expect::string(),
])->castTo('array'),
])->castTo(RendererConfig::class),
])->castTo(AmpClientConfig::class);
}

Expand Down Expand Up @@ -106,16 +138,33 @@ public function loadConfiguration(): void
->setAutowired(false)
->setType(HttpClientFactoryInterface::class)
->setFactory(HttpClientFactory::class, [
'responseHydrator' => $this->prefix('@responseHydrator'),
'responseHydrator' => new Reference($this->prefix('responseHydrator')),
'guzzleClientConfig' => $config->http->guzzle_config,
]);

$builder->addDefinition($this->prefix('ampClient'))
->setType(AmpClientInterface::class)
->setFactory(AmpClient::class, [
'config' => $this->prefix('@config'),
'httpClientFactory' => $this->prefix('@httpClientFactory'),
'cacheStorage' => $this->prefix('@cacheStorage'),
'config' => new Reference($this->prefix('config')),
'httpClientFactory' => new Reference($this->prefix('httpClientFactory')),
'cacheStorage' => new Reference($this->prefix('cacheStorage')),
]);

$builder->addDefinition($this->prefix('renderer.rendererBridge'))
->setAutowired(false)
->setType(RendererBridgeInterface::class)
->setFactory($this->resolveRendererBridgeCreator($config->renderer));

$builder->addDefinition($this->prefix('renderer.bannersResolver'))
->setAutowired(false)
->setType(BannersResolverInterface::class)
->setFactory(BannersResolver::class);

$builder->addDefinition($this->prefix('renderer'))
->setType(RendererInterface::class)
->setFactory(Renderer::class, [
'bannersResolver' => new Reference($this->prefix('renderer.bannersResolver')),
'rendererBridge' => new Reference($this->prefix('renderer.rendererBridge')),
]);
}

Expand Down Expand Up @@ -187,4 +236,60 @@ private function createClientConfigCreator(AmpClientConfig $config): Statement

return $clientConfigFactory;
}

private function resolveRendererBridgeCreator(RendererConfig $config): Statement
{
$rendererBridge = $config->bridge ?? ($this->extensionExists(LatteExtension::class) ? 'latte' : 'phtml');

if (is_string($rendererBridge) && isset(self::RendererBridges[$rendererBridge])) {
$rendererBridge = self::RendererBridges[$rendererBridge];
}

if (LatteRendererBridge::class === $rendererBridge) {
if (!$this->extensionExists(LatteExtension::class)) {
throw new RuntimeException(sprintf(
'Renderer of type %s can not be used without the compiler extension of type %s.',
LatteRendererBridge::class,
LatteExtension::class,
));
}

$rendererBridge = new Statement(LatteRendererBridge::class, [
'latteFactory' => new Statement(ClosureLatteFactory::class, [
'factory' => new Statement([Closure::class, 'fromCallable'], [
[new Reference(LatteFactory::class), 'create'],
]),
]),
]);
}

if (!($rendererBridge instanceof Statement)) {
$rendererBridge = new Statement($rendererBridge);
}

$templatesOverride = array_filter([
Templates::TemplateSingle => $config->templates['single'] ?? null,
Templates::TemplateRandom => $config->templates['random'] ?? null,
Templates::TemplateMultiple => $config->templates['multiple'] ?? null,
Templates::TemplateNotFound => $config->templates['not_found'] ?? null,
]);

if (0 < count($templatesOverride)) {
$rendererBridge = new Statement([$rendererBridge, 'overrideTemplates'], [
'templates' => new Statement(Templates::class, [
'filesMap' => $templatesOverride,
]),
]);
}

return $rendererBridge;
}

/**
* @param class-string $classname
*/
private function extensionExists(string $classname): bool
{
return 0 < count($this->compiler->getExtensions($classname));
}
}
2 changes: 2 additions & 0 deletions src/Bridge/Nette/DI/Config/AmpClientConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ final class AmpClientConfig
public CacheConfig $cache;

public HttpConfig $http;

public RendererConfig $renderer;
}
16 changes: 16 additions & 0 deletions src/Bridge/Nette/DI/Config/RendererConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\AmpClient\Bridge\Nette\DI\Config;

use Nette\DI\Definitions\Statement;

final class RendererConfig
{
/** @var string|Statement|null */
public $bridge = null;

/** @var array<string, string> */
public array $templates = [];
}
10 changes: 10 additions & 0 deletions src/Exception/RendererException.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ public static function rendererBridgeThrownError(string $rendererBridgeClassname
);
}

public static function templateFileNotDefined(string $type): self
{
return new self(
sprintf(
'Template file of type "%s" not defined.',
$type,
),
);
}

public static function templateFileNotFound(string $filename): self
{
return new self(
Expand Down
32 changes: 32 additions & 0 deletions src/Renderer/Latte/ClosureLatteFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\AmpClient\Renderer\Latte;

use Closure;
use Latte\Engine;
use function assert;

final class ClosureLatteFactory implements LatteFactoryInterface
{
/** @var Closure(): Engine */
private Closure $factory;

/**
* @param Closure(): Engine $factory
*/
public function __construct(Closure $factory)
{
$this->factory = $factory;
}

public function create(): Engine
{
$latte = ($this->factory)();

assert($latte instanceof Engine);

return $latte;
}
}
12 changes: 12 additions & 0 deletions src/Renderer/Latte/LatteFactoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\AmpClient\Renderer\Latte;

use Latte\Engine;

interface LatteFactoryInterface
{
public function create(): Engine;
}
91 changes: 91 additions & 0 deletions src/Renderer/Latte/LatteRendererBridge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\AmpClient\Renderer\Latte;

use Latte\Engine;
use SixtyEightPublishers\AmpClient\Renderer\Latte\Templates\MultipleTemplate;
use SixtyEightPublishers\AmpClient\Renderer\Latte\Templates\NotFoundTemplate;
use SixtyEightPublishers\AmpClient\Renderer\Latte\Templates\RandomTemplate;
use SixtyEightPublishers\AmpClient\Renderer\Latte\Templates\SingleTemplate;
use SixtyEightPublishers\AmpClient\Renderer\RendererBridgeInterface;
use SixtyEightPublishers\AmpClient\Renderer\Templates;
use SixtyEightPublishers\AmpClient\Response\ValueObject\Banner;
use SixtyEightPublishers\AmpClient\Response\ValueObject\Position;

final class LatteRendererBridge implements RendererBridgeInterface
{
private LatteFactoryInterface $latteFactory;

private Templates $templates;

private ?Engine $latte = null;

public function __construct(LatteFactoryInterface $latteFactory)
{
$this->latteFactory = $latteFactory;
$this->templates = new Templates([
Templates::TemplateSingle => __DIR__ . '/Templates/single.latte',
Templates::TemplateRandom => __DIR__ . '/Templates/random.latte',
Templates::TemplateMultiple => __DIR__ . '/Templates/multiple.latte',
Templates::TemplateNotFound => __DIR__ . '/Templates/notFound.latte',
]);
}

public static function fromEngine(Engine $engine): self
{
return new self(
new ClosureLatteFactory(static fn (): Engine => $engine),
);
}

public function overrideTemplates(Templates $templates): self
{
$renderer = clone $this;
$renderer->templates = $this->templates->override($templates);

return $renderer;
}

public function renderNotFound(Position $position): string
{
return $this->getLatte()->renderToString(
$this->templates->getTemplateFile(Templates::TemplateNotFound),
new NotFoundTemplate($position),
);
}

public function renderSingle(Position $position, ?Banner $banner): string
{
return $this->getLatte()->renderToString(
$this->templates->getTemplateFile(Templates::TemplateSingle),
new SingleTemplate($position, $banner),
);
}

public function renderRandom(Position $position, ?Banner $banner): string
{
return $this->getLatte()->renderToString(
$this->templates->getTemplateFile(Templates::TemplateRandom),
new RandomTemplate($position, $banner),
);
}

public function renderMultiple(Position $position, array $banners): string
{
return $this->getLatte()->renderToString(
$this->templates->getTemplateFile(Templates::TemplateMultiple),
new MultipleTemplate($position, $banners),
);
}

private function getLatte(): Engine
{
if (null === $this->latte) {
$this->latte = $this->latteFactory->create();
}

return $this->latte;
}
}
27 changes: 27 additions & 0 deletions src/Renderer/Latte/Templates/MultipleTemplate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\AmpClient\Renderer\Latte\Templates;

use SixtyEightPublishers\AmpClient\Response\ValueObject\Banner;
use SixtyEightPublishers\AmpClient\Response\ValueObject\Position;

final class MultipleTemplate
{
public Position $position;

/** @var array<int, Banner> */
public array $banners;

/**
* @param array<int, Banner> $banners
*/
public function __construct(
Position $position,
array $banners
) {
$this->position = $position;
$this->banners = $banners;
}
}
Loading

0 comments on commit 7226b67

Please sign in to comment.