From 1334278e37140fa88ec081d5a6315b0dcd66f82b Mon Sep 17 00:00:00 2001 From: Dave M Date: Wed, 21 Oct 2020 03:20:35 +0000 Subject: [PATCH] - Added basic exception remodeling, seaking original template sources. --- src/Builder.php | 2 + src/Builder/ClassTemplate.php | 112 +++++++++++++--------- src/Compiler.php | 2 - src/ControlStructure/SectionToken.php | 6 +- src/Exception/RenderingError.php | 129 ++++++++++++++++++++++++++ src/FileFetcher.php | 7 +- src/Picea.php | 31 ++++++- 7 files changed, 232 insertions(+), 57 deletions(-) create mode 100644 src/Exception/RenderingError.php diff --git a/src/Builder.php b/src/Builder.php index 08b43f5..aaae8fb 100644 --- a/src/Builder.php +++ b/src/Builder.php @@ -22,6 +22,8 @@ class Builder '%USE%' => ( $uses = $context->renderUses() ) ? "use $uses;" : false, '%CLASSNAME%' => $context->className, '%PATHNAME%' => $context->viewPath, + '%FULLPATH%' => $context->filePath, + '%TEMPLATE%' => $this->templatePath, '%EXTENDS%' => $context->extendFrom ? "extends " . static::TEMPLATE_CLASSNAME_PREFIX . static::generateClassUID($context->extendFrom) : '', '%EXTENDS_TEMPLATE%' => $context->extendFrom, '%CONTENT%' => $compiledSource, diff --git a/src/Builder/ClassTemplate.php b/src/Builder/ClassTemplate.php index 6e42cc2..2092f1d 100644 --- a/src/Builder/ClassTemplate.php +++ b/src/Builder/ClassTemplate.php @@ -5,65 +5,85 @@ namespace %NAMESPACE%; %USE% # %PATHNAME% -class %CLASSNAME% %EXTENDS% { - public array $blockList = []; - public array $sectionList = []; +if (! class_exists("%NAMESPACE%\%CLASSNAME%", false) ) { - public array $variableList = []; + class %CLASSNAME% %EXTENDS% { + public array $blockList = []; - public ?object $thisProxy = null; + public array $sectionList = []; - public \Picea\Picea $picea; + public array $variableList = []; - public static $context; - - public function __construct(\Picea\Picea $picea, array $variablesList = [], ?object $thisProxy = null) { - $this->picea = $picea; - $this->variableList = $variablesList; - $this->thisProxy = $thisProxy; - $this->exportFunctions(); - - static::$context = $picea->context; - } + public ?object $thisProxy = null; - public function output(array $variablesList = []) : void - { - ( function($___class__template, $___global_variables, $___variables, $picea) { - extract($___global_variables); - extract($___variables, \EXTR_OVERWRITE); - ?>%CONTENT%call($this->thisProxy ?? new class(){}, $this, $this->variableList, $variablesList, $this->picea); - %PARENT_OUTPUT% - } + public \Picea\Picea $picea; - public function renderSection($name) : void - { - foreach([ 'prepend', 'default', 'append' ] as $item) { - usort($this->sectionList[$name][$item], fn($a, $b) => $a['order'] <=> $b['order']); + public static $context; - foreach($this->sectionList[$name][$item] as $section) { - $section['callback'](); + public function __construct(\Picea\Picea $picea, array $variablesList = [], ?object $thisProxy = null) { + $this->picea = $picea; + $this->variableList = $variablesList; + $this->thisProxy = $thisProxy; + $this->exportFunctions(); - if ( $item === 'default' ) { - break 1; + static::$context = $picea->context; + } + + public function output(array $variablesList = []) : void + { + ( function($___class__template, $___global_variables, $___variables, $picea) { + extract($___global_variables); + extract($___variables, \EXTR_OVERWRITE); + ?>%CONTENT%call($this->thisProxy ?? new class(){}, $this, $this->variableList, $variablesList, $this->picea); + %PARENT_OUTPUT% + } + + public function renderSection($name) : void + { + foreach([ 'prepend', 'default', 'append' ] as $item) { + usort($this->sectionList[$name][$item], fn($a, $b) => $a['order'] <=> $b['order']); + + foreach($this->sectionList[$name][$item] as $section) { + $section['callback'](); + + if ( $item === 'default' ) { + break 1; + } } } } - } - - public function exportFunctions() : void - { - static $caching = []; - %FUNCTIONS% + + public function exportFunctions() : void + { + static $caching = []; + %FUNCTIONS% + } + + public function getSourceLineFromException(\Throwable $ex) : ? int + { + $sourceFile = file_get_contents("%TEMPLATE%"); + + if ( $sourceFile ) { + foreach(explode(PHP_EOL, $sourceFile) as $line => $content) { + if ( strpos($content, str_replace('$', '%', '$CONTENT$')) !== false ) { + return $ex->getLine() - $line; + } + } + } + + return null; + } + + public function __invoke(array $variablesList = []) : string + { + ob_start(); + $this->output($variablesList); + return ob_get_clean(); + } } - public function __invoke(array $variablesList = []) : string - { - ob_start(); - $this->output($variablesList); - return ob_get_clean(); - } } -return [ 'classname' => "%CLASSNAME%", 'namespace' => "%NAMESPACE%", 'extends' => "%EXTENDS_TEMPLATE%" ]; +return [ 'classname' => "%CLASSNAME%", 'namespace' => "%NAMESPACE%", 'extends' => "%EXTENDS_TEMPLATE%", 'view' => "%FULLPATH%" ]; diff --git a/src/Compiler.php b/src/Compiler.php index 81b1157..977d4e4 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -45,8 +45,6 @@ class Compiler $this->sourceCode = preg_replace_callback($replace, function ($matches) use (&$context) { $matches[2] = trim($matches[2]); - - list($token, $arguments) = array_pad(array_filter(explode(' ', $matches[2], 2), 'strlen'), 2, null); $token = trim($token); diff --git a/src/ControlStructure/SectionToken.php b/src/ControlStructure/SectionToken.php index e7ad623..196d539 100644 --- a/src/ControlStructure/SectionToken.php +++ b/src/ControlStructure/SectionToken.php @@ -45,9 +45,9 @@ class SectionToken implements ControlStructure { $order = $options['order'] ?? "count(\$___class__template->sectionList[$name]['$action'])"; - return "sectionList[$name] ??= [ 'prepend' => [], 'append' => [], 'default' => [] ]; - \$___class__template->sectionList[$name]['$action'][] = [ 'order' => $order, 'callback' => function() use (\$picea, \$___class__template, \$___global_variables, \$___variables) { - extract(\$___global_variables); extract(\$___variables, \EXTR_OVERWRITE); ?>"; + return "sectionList[$name] ??= [ 'prepend' => [], 'append' => [], 'default' => [] ];". + "\$___class__template->sectionList[$name]['$action'][] = [ 'order' => $order, 'callback' => function() use (\$picea, \$___class__template, \$___global_variables, \$___variables) {". + "extract(\$___global_variables); extract(\$___variables, \EXTR_OVERWRITE); ?>"; } protected function printEndSection($context) : string diff --git a/src/Exception/RenderingError.php b/src/Exception/RenderingError.php new file mode 100644 index 0000000..a31acca --- /dev/null +++ b/src/Exception/RenderingError.php @@ -0,0 +1,129 @@ + + */ +class RenderingError extends \Exception +{ + /** + * Constructor. + * + * By default, automatic guessing is enabled. + * + * @param string $message The error message + * @param int $lineno The template line where the error occurred + * @param Source|null $source The source context where the error occurred + */ + public function __construct(string $message, Source $source = null, \Exception $previous = null) + { + parent::__construct('', 0, $previous); + + if (null === $source) { + $name = null; + } else { + $name = $source->getName(); + $this->sourceCode = $source->getCode(); + $this->sourcePath = $source->getPath(); + } + + $this->lineno = $lineno; + $this->name = $name; + $this->rawMessage = $message; + + + } + + public static function generateFromCompiledClass(\Throwable $exception) : self + { + + + return $exception; + } + + private function readTemplateInformation(): void + { + $template = null; + $templateClass = null; + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT); + foreach ($backtrace as $trace) { + if (isset($trace['object']) && $trace['object'] instanceof Template) { + $currentClass = \get_class($trace['object']); + $isEmbedContainer = 0 === strpos($templateClass, $currentClass); + if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) { + $template = $trace['object']; + $templateClass = \get_class($trace['object']); + } + } + } + + // update template name + if (null !== $template && null === $this->name) { + $this->name = $template->getTemplateName(); + } + + // update template path if any + if (null !== $template && null === $this->sourcePath) { + $src = $template->getSourceContext(); + $this->sourceCode = $src->getCode(); + $this->sourcePath = $src->getPath(); + } + + if (null === $template || $this->lineno > -1) { + return; + } + + $r = new \ReflectionObject($template); + $file = $r->getFileName(); + + $exceptions = [$e = $this]; + while ($e = $e->getPrevious()) { + $exceptions[] = $e; + } + + while ($e = array_pop($exceptions)) { + $traces = $e->getTrace(); + array_unshift($traces, ['file' => $e->getFile(), 'line' => $e->getLine()]); + + while ($trace = array_shift($traces)) { + if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) { + continue; + } + + foreach ($template->getDebugInfo() as $codeLine => $templateLine) { + if ($codeLine <= $trace['line']) { + // update template line + $this->lineno = $templateLine; + + return; + } + } + } + } + } +} diff --git a/src/FileFetcher.php b/src/FileFetcher.php index 7a069c2..8f97657 100644 --- a/src/FileFetcher.php +++ b/src/FileFetcher.php @@ -53,8 +53,13 @@ class FileFetcher throw new \RuntimeException("Given view file `$fileName` can not be found within given folder list.."); } + public function getFilePath(string $fileName) : string + { + return $this->findFile($fileName); + } + public function getFileContent(string $fileName) : string { - return file_get_contents($this->findFile($fileName)); + return file_get_contents($this->getFilePath($fileName)); } } diff --git a/src/Picea.php b/src/Picea.php index bffa014..c65c157 100644 --- a/src/Picea.php +++ b/src/Picea.php @@ -3,7 +3,9 @@ namespace Picea; use Closure; -use Picea\Language\LanguageRegistration; +use Picea\Language\LanguageRegistration, + Picea\Exception\RenderingError; + use Psr\Http\Message\ResponseInterface; class Picea implements LanguageRegistration @@ -49,15 +51,33 @@ class Picea implements LanguageRegistration $this->renderContext($this->context); } - public function renderHtml(string $viewPath, array $variables = [], ?object $proxy = null) : string + public function renderHtml(string $viewPath, array $variables = [], ?object $proxy = null) : ? string { if ( null === $object = $this->fetchFromCache($viewPath, $variables, $proxy) ) { throw new \RuntimeException("An error occured while trying to save a compiled template."); } - return $object(); + try { + return $object(); + } + catch(\Throwable $ex) { + # Temporary class for an experiment + throw new class($object, "An error occurred trying to render HTML view `$viewPath` : " . $ex->getMessage(), 911, $ex) extends \Exception { + public function __construct(object $compiledObject, string $message, int $code, \Throwable $previous) + { + parent::__construct($message, $code, $previous); + + $classContent = include( $previous->getFile() ); + + if ( is_array($classContent) ) { + $this->file = $classContent['view']; + $this->line = $compiledObject->getSourceLineFromException($previous); + } + } + }; + } } - + /** * Method used by Block and View tokens * @param object $proxy @@ -68,7 +88,7 @@ class Picea implements LanguageRegistration public function inlineHtml(? object $proxy, string $viewPath, ... $variables) { return $this->renderHtml($viewPath, [ 'inlineVariables' => $variables ], $proxy); } - + public function renderContext(Compiler\Context $context) : object { if ( null === $object = $this->contextFromCache($context) ) { @@ -207,6 +227,7 @@ class Picea implements LanguageRegistration $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";