src/Phug/Renderer/Renderer/Partial/AdapterTrait.php
<?php
namespace Phug\Renderer\Partial;
use ErrorException;
use Phug\Compiler\LocatorInterface;
use Phug\Compiler\WithUpperLocatorInterface;
use Phug\Renderer\Adapter\FileAdapter;
use Phug\Renderer\AdapterInterface;
use Phug\Renderer\CacheInterface;
use Phug\Renderer\Event\HtmlEvent;
use Phug\Renderer\Event\RenderEvent;
use Phug\RendererException;
use Phug\Util\SandBox;
/**
* Trait AdapterTrait: require ModuleContainerInterface to be implemented.
*/
trait AdapterTrait
{
/**
* @var AdapterInterface
*/
private $adapter;
/**
* Fallback to FileAdapter if the current adapter (typically an adapter) is not a cache adapter
* (implement CacheInterface).
*/
private function expectCacheAdapter()
{
if (!($this->adapter instanceof CacheInterface)) {
$this->setAdapterClassName(FileAdapter::class);
}
}
/**
* Create/reset if needed the adapter.
*
* @throws RendererException
*
* @return $this
*/
public function initAdapter()
{
$adapterClassName = $this->getOption('adapter_class_name');
if (!$this->adapter || !is_a($this->adapter, $adapterClassName)) {
if (!is_a($adapterClassName, AdapterInterface::class, true)) {
throw new RendererException(
"Passed adapter class $adapterClassName is ".
'not a valid '.AdapterInterface::class
);
}
$this->adapter = new $adapterClassName($this, $this->getOptions());
$this->initAdapterLinkToCompiler();
}
return $this;
}
public function initAdapterLinkToCompiler()
{
$compiler = $this->getCompiler();
if ($compiler &&
$this->adapter &&
$this->adapter instanceof LocatorInterface &&
$compiler instanceof WithUpperLocatorInterface
) {
$compiler->setUpperLocator($this->adapter);
}
}
/**
* Get the current adapter used (file, stream, eval or custom adapter provided).
*
* @return AdapterInterface
*/
public function getAdapter()
{
return $this->adapter;
}
/**
* Set the current adapter engine (file, stream, eval or custom adapter provided).
*
* @return $this
*/
public function setAdapterClassName($adapterClassName)
{
return $this->setOption('adapter_class_name', $adapterClassName)->initAdapter();
}
/**
* Return a sandbox with renderer settings for a given callable action.
*
* @param callable $action
*
* @return SandBox
*/
public function getNewSandBox(callable $action)
{
$errorHandler = $this->getOption('error_reporting');
if ($errorHandler !== null && !is_callable($errorHandler)) {
$errorReporting = $errorHandler;
$errorHandler = function ($number, $message, $file, $line) use ($errorReporting) {
if ($errorReporting & $number) {
throw new ErrorException($message, 0, $number, $file, $line);
}
return true;
};
}
return new SandBox($action, $errorHandler);
}
/**
* Call an adapter method inside a sandbox and return the SandBox result.
*
* @param string $source
* @param string $method
* @param string $path
* @param string $input
* @param callable $getSource
* @param array $parameters
*
* @return SandBox
*/
private function getSandboxCall(&$source, $method, $path, $input, callable $getSource, array $parameters)
{
return $this->getNewSandBox(function () use (&$source, $method, $path, $input, $getSource, $parameters) {
$adapter = $this->getAdapter();
$cacheEnabled = (
$adapter->hasOption('cache_dir') && $adapter->getOption('cache_dir') ||
$this->hasOption('cache_dir') && $this->getOption('cache_dir')
);
if ($cacheEnabled) {
$this->expectCacheAdapter();
$adapter = $this->getAdapter();
$display = function () use ($adapter, $path, $input, $getSource, $parameters) {
/* @var CacheInterface $adapter */
$adapter->displayCached($path, $input, $getSource, $parameters);
};
return in_array($method, ['display', 'displayFile'], true)
? $display()
: $adapter->captureBuffer($display);
}
$source = $getSource($path, $input);
return $adapter->$method(
$source,
$parameters
);
});
}
/**
* Handle an html event and accordingly to it, display, returns or throw the result/error.
*
* @param HtmlEvent $htmlEvent
* @param array $parameters
* @param callable $getSource
*
* @throws RendererException|\Throwable
*
* @return mixed
*/
private function handleHtmlEvent(HtmlEvent $htmlEvent, array $parameters, $path, callable $getSource)
{
if ($error = $htmlEvent->getError()) {
$this->handleError($error, 1, $path, $getSource(), $parameters, [
'debug' => $this->getOption('debug'),
'exit_on_error' => $this->getOption('exit_on_error'),
'error_handler' => $this->getOption('error_handler'),
'html_error' => $this->getOption('html_error'),
'error_context_lines' => $this->getOption('error_context_lines'),
'color_support' => $this->getOption('color_support'),
]);
}
if ($buffer = $htmlEvent->getBuffer()) {
echo $buffer;
}
return $htmlEvent->getResult();
}
/**
* Call a method on the adapter (render, renderFile, display, displayFile, more methods can be available depending
* on the adapter user).
*
* @param string $method
* @param string $path
* @param string $input
* @param callable $getSource
* @param array $parameters
*
* @throws RendererException|\Throwable
*
* @return bool|string|null
*/
public function callAdapter($method, $path, $input, callable $getSource, array $parameters)
{
$source = '';
$this->getCompiler()->reset();
$renderEvent = new RenderEvent($input, $path, $method, $parameters);
$this->trigger($renderEvent);
$input = $renderEvent->getInput();
$path = $renderEvent->getPath();
$method = $renderEvent->getMethod();
$parameters = $this->mergeWithSharedVariables($renderEvent->getParameters());
if ($self = $this->getOption('self')) {
$self = $self === true ? 'self' : strval($self);
$parameters = [
$self => $parameters,
];
}
$sandBox = $this->getSandboxCall($source, $method, $path, $input, $getSource, $parameters);
$htmlEvent = new HtmlEvent(
$renderEvent,
$sandBox->getResult(),
$sandBox->getBuffer(),
$sandBox->getThrowable()
);
$this->trigger($htmlEvent);
$sourceOnDemand = function () use ($source, $getSource, $path, $input) {
return $source ?: $getSource($path, $input);
};
return $this->handleHtmlEvent($htmlEvent, $parameters, $path, $sourceOnDemand);
}
}