picea/src/Picea.php
2023-11-08 06:54:30 -05:00

262 lines
8.4 KiB
PHP

<?php declare(strict_types=1);
namespace Picea;
use Closure;
use Picea\Language\LanguageRegistration,
Picea\Exception\RenderingError;
use Psr\Http\Message\ResponseInterface;
class Picea implements LanguageRegistration
{
const DEFAULT_BUILDER_TEMPLATE = "/Builder/ClassTemplate.php";
public Compiler\Context $context;
public Compiler $compiler;
public LanguageRegistration $languageRegistration;
public string $builderTemplatePath;
public Caching\Cache $cache;
public FileFetcher $fileFetcher;
public bool $debug;
public array $loadedTemplateFile = [];
public array $globalVariables = [];
public array $compiled = [];
public function __construct(
? Compiler\Context $context = null,
? Caching\Cache $cache = null,
? Compiler $compiler = null,
? LanguageRegistration $languageRegistration = null,
? FileFetcher $fileFetcher = null,
? string $builderTemplatePath = null,
bool $debug = false
){
$this->cache = $cache ?? new Caching\Memory("");
$this->context = $context ?? new Compiler\BaseContext();
$this->languageRegistration = $languageRegistration ?? new Language\DefaultRegistrations([], [], []);
$this->builderTemplatePath = $builderTemplatePath ?? dirname(__FILE__) . static::DEFAULT_BUILDER_TEMPLATE;
$this->compiler = $compiler ?? $this->instanciateCompiler();
$this->fileFetcher = $fileFetcher ?? $this->instanciateFileFetcher();
$this->debug = $debug;
$this->compiler->registerExtensionsFunctions($this->context);
$this->renderContext($this->context);
}
public function gatherCompiledObject(string $viewPath, array $variables = [], ? object $proxy = null) : ? object
{
if ( null === $object = $this->fetchFromCache($viewPath, $this->globalVariables + $variables, $proxy) ) {
throw new \RuntimeException("An error occured while trying to save a compiled template.");
}
return $object;
}
public function renderHtml(string $viewPath, array $variables = [], ?object $proxy = null) : ? string
{
$object = $this->gatherCompiledObject($viewPath, $variables, $proxy);
try {
return call_user_func($object);
}
catch(\Throwable $ex) {
#if (! $ex instanceof Exception\RenderHtmlException ) {
# throw new Exception\RenderHtmlException($object, $this, "An error occurred trying to render HTML view `$viewPath` : " . $ex->getMessage(), 911, $ex);
#}
#else {
throw $ex;
#}
}
return null;
}
/**
* Method used by Block and View tokens
*/
public function inlineHtml(? object $proxy, string $viewPath, array $variables) {
return $this->renderHtml($viewPath, $this->globalVariables + $variables, $proxy);
}
/**
* Allows block to be called from templates
*/
public function inlineBlock(? object $proxy, string $viewPath, ... $variables) {
return $this->renderHtml($viewPath, [ 'inlineVariables' => $variables, 'globalVariables' => $this->globalVariables ], $proxy);
}
/**
* Push view content inline
*/
public function inlineContent(string $viewPath) {
return $this->fileFetcher->getFileContent($viewPath);
}
/**
* Renders a compile context
*/
public function renderContext(Compiler\Context $context) : object
{
if ( null === $object = $this->contextFromCache($context) ) {
throw new \RuntimeException("An error occured while trying to save a compiled template.");
}
return $object;
}
public function compileSource(string $source) : array
{
$this->compiler->loadSourceCode($source);
return [
'context' => $context = clone $this->context,
'source' => $this->compiler->compile($context),
];
}
public function compileFile(string $viewPath) : array
{
if (! file_exists($viewPath) ) {
throw new \InvalidArgumentException("File `$viewPath` cannot be found or there is a permission problem.");
}
if ( false === $fileContent = file_get_contents($viewPath) ) {
throw new \ErrorException("Given file could not be opened `$viewPath`. This could indicate a permission misconfiguration on your file or folder.");
}
$this->compiler->loadSourceCode($fileContent);
$context = clone $this->context;
$context->viewPath = $viewPath;
$context->source = $this->compiler->compile($context);
return $context;
}
public function buildFromSource(string $source) : array
{
$tmpFolder = sys_get_temp_dir();
$builder = $this->instanciateBuilder();
$compiledSource = $this->compileSource($source);
list($namespace, $className) = $builder->build($compiledSource['context'], $compiledSource['source']) ;
$path = "$tmpFolder/$className.php";
file_put_contents($path, $compiledSource);
return [
'path' => $path,
'namespace' => $namespace,
'className' => $className,
];
}
public function fetchFromCache(string $viewPath, array $variables = [], ?object $proxy = null) : ?object
{
if ( ! isset($this->compiled[$viewPath]) && ( true === $this->debug ) || (! $this->cache->compiled($viewPath))) {
$context = $this->compileView($viewPath);
$this->cache->save($context);
if ( $context->extendFrom ) {
$this->fetchFromCache($context->extendFrom, $variables, $proxy);
}
$this->compiled[$viewPath] = true;
}
return $this->cache->load($viewPath, $this, $variables, $proxy);
}
public function contextFromCache(Compiler\Context $context) : ?object
{
if ( $this->debug || ! $this->cache->compiled( $context->cacheFilename() ) ) {
$context = $this->compileContext($context);
$this->cache->save($context);
}
return $this->cache->load($context->cacheFilename(), $this);
}
public function instanciateCompiler() : Compiler
{
return new Compiler($this->languageRegistration);
}
public function instanciateBuilder() : Builder
{
return new Builder($this->builderTemplatePath);
}
public function instanciateFileFetcher() : FileFetcher
{
return new FileFetcher();
}
public function registerAll(Compiler $compiler) : void
{
$this->registerSyntax($compiler);
$this->registerControlStructure($compiler);
$this->registerExtension($compiler);
}
public function registerSyntax(Compiler $compiler) : void
{
$this->languageRegistration->registerSyntax($compiler);
}
public function registerControlStructure(Compiler $compiler) : void
{
$this->languageRegistration->registerControlStructure($compiler);
}
public function registerExtension(Compiler $compiler) : void
{
$this->languageRegistration->registerExtension($compiler);
}
public function declareLoadedTemplate(string $className, string $filePath) : void
{
$this->loadedTemplateFile[$className] = $filePath;
}
protected function compileView(string $viewPath) : Compiler\Context
{
$tmpFolder = sys_get_temp_dir();
$builder = $this->instanciateBuilder();
$compiled = $this->compileSource($this->fileFetcher->getFileContent($viewPath));
$context = $compiled['context'];
$context->viewPath = $viewPath;
$context->filePath = $this->fileFetcher->getFilePath($viewPath);
$context = $builder->build($compiled['context'], $compiled['source']) ;
$context->classPath = $tmpFolder . DIRECTORY_SEPARATOR . $context->className . ".php";
if ( $context->extendFrom ) {
$this->compileView($context->extendFrom);
}
return $context;
}
protected function compileContext(Compiler\Context $context) : Compiler\Context
{
$tmpFolder = sys_get_temp_dir();
$builder = $this->instanciateBuilder();
$compiled = $this->compileSource("");
$context->viewPath = $context->cacheFilename();
$builder->build($context, "");
$context->classPath = $tmpFolder . DIRECTORY_SEPARATOR . $context->cacheFilename() . ".php";
return $context;
}
}