Skip to content

Commit

Permalink
added {trace}
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Jan 25, 2021
1 parent e208345 commit 76eb04a
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/Latte/Macros/CoreMacros.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public static function install(Latte\Compiler $compiler): void
$me->addMacro('default', [$me, 'macroVar']);
$me->addMacro('dump', [$me, 'macroDump']);
$me->addMacro('debugbreak', [$me, 'macroDebugbreak']);
$me->addMacro('trace', 'LR\Tracer::throw() %node.line;');
$me->addMacro('l', '?>{<?php');
$me->addMacro('r', '?>}<?php');

Expand Down
115 changes: 115 additions & 0 deletions src/Latte/Runtime/Tracer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

/**
* This file is part of the Latte (https://latte.nette.org)
* Copyright (c) 2008 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Latte\Runtime;

use Latte;
use Latte\Engine;


/**
* @internal
*/
class Tracer
{
public static function throw(): void
{
$e = new Latte\RuntimeException('Your location in Latte templates');
$trace = debug_backtrace();
$props = [
'file' => $trace[1]['object']->getName(),
'line' => self::getSourceLine($trace[0]['file'], $trace[0]['line']),
'trace' => self::generateTrace($trace),
];
foreach ($props as $name => $value) {
$ref = new \ReflectionProperty('Exception', $name);
$ref->setAccessible(true);
$ref->setValue($e, $value);
}

throw $e;
}


private static function generateTrace(array $trace): array
{
$res = [];
foreach ($trace as $i => $item) {
$object = $item['object'] ?? null;
if ($object instanceof Template) {
$method = $item['function'] ?? '';

if (Latte\Helpers::startsWith($method, 'block')) {
// begin of block
$comment = (new \ReflectionMethod($object, $method))->getDocComment();
$res[] = [
'function' => preg_match('~(\{.+\})~', $comment, $m) ? $m[1] : '?',
'file' => $object->getName(),
'line' => preg_match('~ on line (\d+)~', $comment, $m) ? (int) $m[1] : 0,
'args' => [], // $L_args is not true, will be added in next step
];

} elseif ($method === 'render' && $object->getReferenceType()) {
// begin of included/extended/... file
$res[] = [
'function' => '{' . $object->getReferenceType() . ' ' . basename($object->getName()) . '}',
'file' => $object->getReferringTemplate()->getName(),
'line' => 0, // will be added in next step
'args' => self::filterParams($object->getParameters()),
];

} elseif ($method === 'renderToContentType') {
// {include file}, extends, embed, sandbox, ...
$res[count($res) - 1]['line'] = self::getSourceLine($item['file'], $item['line']);

} elseif ($method === 'renderBlock' || $method === 'renderBlockParent') {
// {include block}
$res[count($res) - 1]['args'] = self::filterParams($item['args'][1] + $object->getParameters());

if ($method !== 'renderBlock' || isset($item['args'][2])) { // is not {block}
$res[] = [
'function' => '{include ' . ($method === 'renderBlockParent' ? 'parent' : $item['args'][0]) . '}',
'file' => $object->getName(),
'line' => self::getSourceLine($item['file'], $item['line']),
'args' => self::filterParams($item['args'][1]),
];
}
}

} elseif ($object instanceof Engine) {
break;
}
}
return $res;
}


private static function getSourceLine(string $compiledFile, int $line): int
{
if (!is_file($compiledFile)) {
return 0;
}
$line = file($compiledFile)[$line - 1];
return preg_match('~/\* line (\d+) \*/~', $line, $m)
? (int) $m[1]
: 0;
}


private static function filterParams(array $params): array
{
foreach ($params as $key => $foo) {
if (is_string($key) && Latte\Helpers::startsWith($key, 'ʟ_')) {
unset($params[$key]);
}
}
unset($params['_l'], $params['_g']);
return $params;
}
}
99 changes: 99 additions & 0 deletions tests/Latte/CoreMacros.trace.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

/**
* Test: {trace}
*/

declare(strict_types=1);

use Tester\Assert;

require __DIR__ . '/../bootstrap.php';


$latte = new Latte\Engine;
$latte->setTempDirectory(getTempDir());
$latte->setLoader(new Latte\Loaders\StringLoader([
'parent' => '
{block content}
{/block}
{define inc}
{embed file embed}
{block foo}
{trace}
{/block}
{/embed}
{/define}
',

'main' => '
{extends "parent"}
{block content}
{include inc, var: 123}
{/block}
{block inc}
{include parent, var2: 456}
{/block}
',
'embed' => '{block foo}{/block}',
]));


$e = Assert::exception(function () use ($latte) {
$latte->render('main');
}, Latte\RuntimeException::class, 'Your location in Latte templates');


Assert::same([
[
'function' => '{block foo}',
'file' => 'parent',
'line' => 7,
'args' => [],
],
[
'function' => '{embed embed}',
'file' => 'parent',
'line' => 6,
'args' => [],
],
[
'function' => '{define inc}',
'file' => 'parent',
'line' => 5,
'args' => ['var2' => 456, 'var' => 123],
],
[
'function' => '{include parent}',
'file' => 'main',
'line' => 9,
'args' => ['var2' => 456, 'var' => 123],
],
[
'function' => '{block inc}',
'file' => 'main',
'line' => 8,
'args' => ['var' => 123],
],
[
'function' => '{include inc}',
'file' => 'main',
'line' => 5,
'args' => ['var' => 123],
],
[
'function' => '{block content}',
'file' => 'main',
'line' => 4,
'args' => [],
],
[
'function' => '{extends parent}',
'file' => 'main',
'line' => 0,
'args' => [],
],
], $e->getTrace());

0 comments on commit 76eb04a

Please sign in to comment.