- Added basic exception remodeling, seaking original template sources.

This commit is contained in:
Dave M. 2020-10-21 03:20:35 +00:00
parent 10632e9f67
commit 1334278e37
7 changed files with 232 additions and 57 deletions

View File

@ -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,

View File

@ -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%<?php
} )->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%<?php
} )->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%" ];

View File

@ -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);

View File

@ -45,9 +45,9 @@ class SectionToken implements ControlStructure {
$order = $options['order'] ?? "count(\$___class__template->sectionList[$name]['$action'])";
return "<?php \$___class__template->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 "<?php \$___class__template->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

View File

@ -0,0 +1,129 @@
<?php
namespace Twig\Error;
use Twig\Source;
use Twig\Template;
/**
* Twig base exception.
*
* This exception class and its children must only be used when
* an error occurs during the loading of a template, when a syntax error
* is detected in a template, or when rendering a template. Other
* errors must use regular PHP exception classes (like when the template
* cache directory is not writable for instance).
*
* To help debugging template issues, this class tracks the original template
* name and line where the error occurred.
*
* Whenever possible, you must set these information (original template name
* and line number) yourself by passing them to the constructor. If some or all
* these information are not available from where you throw the exception, then
* this class will guess them automatically (when the line number is set to -1
* and/or the name is set to null). As this is a costly operation, this
* can be disabled by passing false for both the name and the line number
* when creating a new instance of this class.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
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;
}
}
}
}
}
}

View File

@ -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));
}
}

View File

@ -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";