Js-phpize conditional class declaration is forbidden inside template cache class
butteredptarmigan opened this issue · comments
Environment: PHP 7.4 / Symfony 5 / pug-symfony 3.0.1 (but I believe the issue is quite universal)
I have encountered an error while trying to use dot operator in js-style expression mode. The error was: Compile Error: Class declarations may not be nested
. Apparently it is Symfony specific and comes from the fact that a piece of code from js-phpize (where it is executed globally) ends up in another class' context because Twig caches templates as classes. And since class declaration nesting is a forbidden behaviour in PHP, it fails at compilation time.
More specifically, it occurs in this cache template: PugTemplateTemplate, inside doDisplay
method.
And this is the class declaration mentioned above: Dot.h (of course just an example, it is present in the entire family of dot-related helpers).
This is the relevant part from my stack trace:
Symfony\Component\ErrorHandler\Error\FatalError:
Compile Error: Class declarations may not be nested
at path\var\cache\dev\twig\a0\a02227c14d62658903dcfe0bfefc5866bf418ffd0fbf867fce437411ea660efb.php:232
at Twig\Cache\FilesystemCache->load()
(path\vendor\twig\twig\src\Environment.php:350)
at Pug\Twig\Environment->loadTemplate()
(path\vendor\pug-php\pug-symfony\src\Pug\Twig\Environment.php:181)
at Pug\Twig\Environment->loadTemplate()
(path\vendor\twig\twig\src\Template.php:324)
at __TwigTemplate_92cd03e475ea182c4e0fa520682577fcc1b561bcdf27ee46878d7e020e0b8ac2->loadTemplate()
(path\var\cache\dev\twig\02\02fac23e7c032670a706e291cc42e3bbe51e0b5bcd2edd6a5201059c3f843e83.php:109)
Hello, I have plenty of templates using the dot operator that works fine. Please provide a minimum way to reproduce your issue including template and data, and the compiled template in path\var\cache\dev\twig.
Thank you.
Symfony controller:
public function test() {
return $this->render("views/test.pug", [
'pig' => [
'is' => [
'smart' => true
]
]
]);
}
views/test.pug:
if pig.is.smart
p Pigs are exceptionally intelligent.
var/cache/dev/twig/a3/a38515de60aad3c35d4baa83c7190b68ebccfa621e4e82cd3a776ad1135cfe1b.php:
<?php
use Twig\Environment;
use Twig\Source;
use Twig\Template;
/* views/test.pug */
class __TwigTemplate_12ab7c8c0ae87e7828c462002f2b56653bbc981a8275d3320d010ea4a058c9e5 extends Template
{
private $source;
private $macros = [];
public function __construct(Environment $env)
{
parent::__construct($env);
$this->source = $this->getSourceContext();
$this->parent = false;
$this->blocks = [];
}
protected function doDisplay(array $context, array $blocks = [])
{
$macros = $this->macros;
if (isset($context['this'])) {
unset($context['this']);
}
extract($context);
if (isset($this->extensions['Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension'])) {
$__internal_e4f684ed2462fac4cd5575013545d71c5f754446 = $this->extensions['Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension'];
$__internal_e4f684ed2462fac4cd5575013545d71c5f754446->enter($__internal_e4f684ed2462fac4cd5575013545d71c5f754446_prof = new \Twig\Profiler\Profile($this->getTemplateName(), 'template', 'views/test.pug'));
}
if (isset($this->extensions['Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension'])) {
$__internal_d0ad88d8acf2d663bcedeed683cadfd106fb1a4d = $this->extensions['Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension'];
$__internal_d0ad88d8acf2d663bcedeed683cadfd106fb1a4d->enter($__internal_d0ad88d8acf2d663bcedeed683cadfd106fb1a4d_prof = new \Twig\Profiler\Profile($this->getTemplateName(), 'template', 'views/test.pug'));
}
?><?php $GLOBALS['__jpv_dotWithArrayPrototype'] = function ($base) {
$arrayPrototype = function ($base, $key) {
if ($key === 'length') {
return count($base);
}
if ($key === 'forEach') {
return function ($callback, $userData = null) use (&$base) {
return array_walk($base, $callback, $userData);
};
}
if ($key === 'map') {
return function ($callback) use (&$base) {
return array_map($callback, $base);
};
}
if ($key === 'filter') {
return function ($callback, $flag = 0) use ($base) {
return func_num_args() === 1 ? array_filter($base, $callback) : array_filter($base, $callback, $flag);
};
}
if ($key === 'pop') {
return function () use (&$base) {
return array_pop($base);
};
}
if ($key === 'shift') {
return function () use (&$base) {
return array_shift($base);
};
}
if ($key === 'push') {
return function ($item) use (&$base) {
return array_push($base, $item);
};
}
if ($key === 'unshift') {
return function ($item) use (&$base) {
return array_unshift($base, $item);
};
}
if ($key === 'indexOf') {
return function ($item) use (&$base) {
$search = array_search($item, $base);
return $search === false ? -1 : $search;
};
}
if ($key === 'slice') {
return function ($offset, $length = null, $preserveKeys = false) use (&$base) {
return array_slice($base, $offset, $length, $preserveKeys);
};
}
if ($key === 'splice') {
return function ($offset, $length = null, $replacements = array()) use (&$base) {
return array_splice($base, $offset, $length, $replacements);
};
}
if ($key === 'reverse') {
return function () use (&$base) {
return array_reverse($base);
};
}
if ($key === 'reduce') {
return function ($callback, $initial = null) use (&$base) {
return array_reduce($base, $callback, $initial);
};
}
if ($key === 'join') {
return function ($glue) use (&$base) {
return implode($glue, $base);
};
}
if ($key === 'sort') {
return function ($callback = null) use (&$base) {
return $callback ? usort($base, $callback) : sort($base);
};
}
return null;
};
$getFromArray = function ($base, $key) use ($arrayPrototype) {
return isset($base[$key])
? $base[$key]
: $arrayPrototype($base, $key);
};
$getCallable = function ($base, $key) use ($getFromArray) {
if (is_callable(array($base, $key))) {
return new JsPhpizeDotCarrier(array($base, $key));
}
if ($base instanceof \ArrayAccess) {
return $getFromArray($base, $key);
}
};
$getRegExp = function ($value) {
return is_object($value) && isset($value->isRegularExpression) && $value->isRegularExpression ? $value->regExp . $value->flags : null;
};
$fallbackDot = function ($base, $key) use ($getCallable, $getRegExp) {
if (is_string($base)) {
if (preg_match('/^[-+]?\d+$/', strval($key))) {
return substr($base, intval($key), 1);
}
if ($key === 'length') {
return strlen($base);
}
if ($key === 'substr' || $key === 'slice') {
return function ($start, $length = null) use ($base) {
return func_num_args() === 1 ? substr($base, $start) : substr($base, $start, $length);
};
}
if ($key === 'charAt') {
return function ($pos) use ($base) {
return substr($base, $pos, 1);
};
}
if ($key === 'indexOf') {
return function ($needle) use ($base) {
$pos = strpos($base, $needle);
return $pos === false ? -1 : $pos;
};
}
if ($key === 'toUpperCase') {
return function () use ($base) {
return strtoupper($base);
};
}
if ($key === 'toLowerCase') {
return function () use ($base) {
return strtolower($base);
};
}
if ($key === 'match') {
return function ($search) use ($base, $getRegExp) {
$regExp = $getRegExp($search);
$search = $regExp ? $regExp : (is_string($search) ? '/' . preg_quote($search, '/') . '/' : strval($search));
return preg_match($search, $base);
};
}
if ($key === 'split') {
return function ($delimiter) use ($base, $getRegExp) {
if ($regExp = $getRegExp($delimiter)) {
return preg_split($regExp, $base);
}
return explode($delimiter, $base);
};
}
if ($key === 'replace') {
return function ($from, $to) use ($base, $getRegExp) {
if ($regExp = $getRegExp($from)) {
return preg_replace($regExp, $to, $base);
}
return str_replace($from, $to, $base);
};
}
}
return $getCallable($base, $key);
};
foreach (array_slice(func_get_args(), 1) as $key) {
$base = is_array($base)
? $getFromArray($base, $key)
: (is_object($base)
? (isset($base->$key)
? $base->$key
: (method_exists($base, $method = "get" . ucfirst($key))
? $base->$method()
: (method_exists($base, $key)
? array($base, $key)
: $getCallable($base, $key)
)
)
)
: $fallbackDot($base, $key)
);
}
return $base;
};
if (!class_exists(JsPhpizeDotCarrier::class)) {
class JsPhpizeDotCarrier extends \ArrayObject
{
public function getValue()
{
if ($this->isArrayAccessible()) {
return $this[0][$this[1]];
}
return $this[0]->{$this[1]} ?? null;
}
public function setValue($value)
{
if ($this->isArrayAccessible()) {
$this[0][$this[1]] = $value;
return;
}
$this[0]->{$this[1]} = $value;
}
public function getCallable()
{
return $this->getArrayCopy();
}
public function __isset($name)
{
$value = $this->getValue();
if ((is_array($value) || $value instanceof ArrayAccess) && isset($value[$name])) {
return true;
}
return is_object($value) && isset($value->$name);
}
public function __get($name)
{
return new self(array($this->getValue(), $name));
}
public function __set($name, $value)
{
$value = $this->getValue();
if (is_array($value)) {
$value[$name] = $value;
$this->setValue($value);
return;
}
$value->$name = $value;
}
public function __toString()
{
return (string) $this->getValue();
}
public function __toBoolean()
{
$value = $this->getValue();
if (method_exists($value, '__toBoolean')) {
return $value->__toBoolean();
}
return !!$value;
}
public function __invoke(...$arguments)
{
return call_user_func_array($this->getCallable(), $arguments);
}
private function isArrayAccessible()
{
return is_array($this[0]) || $this[0] instanceof ArrayAccess && !isset($this[0]->{$this[1]});
}
}
};
$GLOBALS['__jpv_dotWithArrayPrototype_with_ref'] = function (&$base) {
$arrayPrototype = function (&$base, $key) {
if ($key === 'length') {
return count($base);
}
if ($key === 'forEach') {
return function ($callback, $userData = null) use (&$base) {
return array_walk($base, $callback, $userData);
};
}
if ($key === 'map') {
return function ($callback) use (&$base) {
return array_map($callback, $base);
};
}
if ($key === 'filter') {
return function ($callback, $flag = 0) use ($base) {
return func_num_args() === 1 ? array_filter($base, $callback) : array_filter($base, $callback, $flag);
};
}
if ($key === 'pop') {
return function () use (&$base) {
return array_pop($base);
};
}
if ($key === 'shift') {
return function () use (&$base) {
return array_shift($base);
};
}
if ($key === 'push') {
return function ($item) use (&$base) {
return array_push($base, $item);
};
}
if ($key === 'unshift') {
return function ($item) use (&$base) {
return array_unshift($base, $item);
};
}
if ($key === 'indexOf') {
return function ($item) use (&$base) {
$search = array_search($item, $base);
return $search === false ? -1 : $search;
};
}
if ($key === 'slice') {
return function ($offset, $length = null, $preserveKeys = false) use (&$base) {
return array_slice($base, $offset, $length, $preserveKeys);
};
}
if ($key === 'splice') {
return function ($offset, $length = null, $replacements = array()) use (&$base) {
return array_splice($base, $offset, $length, $replacements);
};
}
if ($key === 'reverse') {
return function () use (&$base) {
return array_reverse($base);
};
}
if ($key === 'reduce') {
return function ($callback, $initial = null) use (&$base) {
return array_reduce($base, $callback, $initial);
};
}
if ($key === 'join') {
return function ($glue) use (&$base) {
return implode($glue, $base);
};
}
if ($key === 'sort') {
return function ($callback = null) use (&$base) {
return $callback ? usort($base, $callback) : sort($base);
};
}
return null;
};
$getFromArray = function (&$base, $key) use ($arrayPrototype) {
return isset($base[$key])
? $base[$key]
: $arrayPrototype($base, $key);
};
$getCallable = function (&$base, $key) use ($getFromArray) {
if (is_callable(array($base, $key))) {
return new JsPhpizeDotCarrier(array($base, $key));
}
if ($base instanceof \ArrayAccess) {
return $getFromArray($base, $key);
}
};
$getRegExp = function ($value) {
return is_object($value) && isset($value->isRegularExpression) && $value->isRegularExpression ? $value->regExp . $value->flags : null;
};
$fallbackDot = function (&$base, $key) use ($getCallable, $getRegExp) {
if (is_string($base)) {
if (preg_match('/^[-+]?\d+$/', strval($key))) {
return substr($base, intval($key), 1);
}
if ($key === 'length') {
return strlen($base);
}
if ($key === 'substr' || $key === 'slice') {
return function ($start, $length = null) use ($base) {
return func_num_args() === 1 ? substr($base, $start) : substr($base, $start, $length);
};
}
if ($key === 'charAt') {
return function ($pos) use ($base) {
return substr($base, $pos, 1);
};
}
if ($key === 'indexOf') {
return function ($needle) use ($base) {
$pos = strpos($base, $needle);
return $pos === false ? -1 : $pos;
};
}
if ($key === 'toUpperCase') {
return function () use ($base) {
return strtoupper($base);
};
}
if ($key === 'toLowerCase') {
return function () use ($base) {
return strtolower($base);
};
}
if ($key === 'match') {
return function ($search) use ($base, $getRegExp) {
$regExp = $getRegExp($search);
$search = $regExp ? $regExp : (is_string($search) ? '/' . preg_quote($search, '/') . '/' : strval($search));
return preg_match($search, $base);
};
}
if ($key === 'split') {
return function ($delimiter) use ($base, $getRegExp) {
if ($regExp = $getRegExp($delimiter)) {
return preg_split($regExp, $base);
}
return explode($delimiter, $base);
};
}
if ($key === 'replace') {
return function ($from, $to) use ($base, $getRegExp) {
if ($regExp = $getRegExp($from)) {
return preg_replace($regExp, $to, $base);
}
return str_replace($from, $to, $base);
};
}
}
return $getCallable($base, $key);
};
$crawler = &$base;
$result = $base;
foreach (array_slice(func_get_args(), 1) as $key) {
$result = is_array($crawler)
? $getFromArray($crawler, $key)
: (is_object($crawler)
? (isset($crawler->$key)
? $crawler->$key
: (method_exists($crawler, $method = "get" . ucfirst($key))
? $crawler->$method()
: (method_exists($crawler, $key)
? array($crawler, $key)
: $getCallable($crawler, $key)
)
)
)
: $fallbackDot($crawler, $key)
);
$crawler = &$result;
}
return $result;
};
if (!class_exists(JsPhpizeDotCarrier::class)) {
class JsPhpizeDotCarrier extends \ArrayObject
{
public function getValue()
{
if ($this->isArrayAccessible()) {
return $this[0][$this[1]];
}
return $this[0]->{$this[1]} ?? null;
}
public function setValue($value)
{
if ($this->isArrayAccessible()) {
$this[0][$this[1]] = $value;
return;
}
$this[0]->{$this[1]} = $value;
}
public function getCallable()
{
return $this->getArrayCopy();
}
public function __isset($name)
{
$value = $this->getValue();
if ((is_array($value) || $value instanceof ArrayAccess) && isset($value[$name])) {
return true;
}
return is_object($value) && isset($value->$name);
}
public function __get($name)
{
return new self(array($this->getValue(), $name));
}
public function __set($name, $value)
{
$value = $this->getValue();
if (is_array($value)) {
$value[$name] = $value;
$this->setValue($value);
return;
}
$value->$name = $value;
}
public function __toString()
{
return (string) $this->getValue();
}
public function __toBoolean()
{
$value = $this->getValue();
if (method_exists($value, '__toBoolean')) {
return $value->__toBoolean();
}
return !!$value;
}
public function __invoke(...$arguments)
{
return call_user_func_array($this->getCallable(), $arguments);
}
private function isArrayAccessible()
{
return is_array($this[0]) || $this[0] instanceof ArrayAccess && !isset($this[0]->{$this[1]});
}
}
};
$firstMixin = function (string ...$names) use (&$__pug_mixins) {
foreach ($names as $name) {
if (isset($__pug_mixins[$name])) {
return $name;
}
}
throw new \InvalidArgumentException("No defined mixin/component in [".implode(", ", $names)."]");
};
$firstComponent = $firstMixin;
\Phug\Renderer\Profiler\ProfilerModule::recordProfilerDisplayEvent(3);
// PUG_DEBUG:3
?><?php
\Phug\Renderer\Profiler\ProfilerModule::recordProfilerDisplayEvent(2);
// PUG_DEBUG:2
?><?php if (method_exists($_pug_temp = $GLOBALS['__jpv_dotWithArrayPrototype_with_ref']($pig, 'is', 'smart'), "__toBoolean")
? $_pug_temp->__toBoolean()
: $_pug_temp) { ?><?php
\Phug\Renderer\Profiler\ProfilerModule::recordProfilerDisplayEvent(1);
// PUG_DEBUG:1
?><p><?php
\Phug\Renderer\Profiler\ProfilerModule::recordProfilerDisplayEvent(0);
// PUG_DEBUG:0
?>Pigs are exceptionally intelligent.</p>
<?php } ?><?php
if (isset($__internal_e4f684ed2462fac4cd5575013545d71c5f754446)) {
$__internal_e4f684ed2462fac4cd5575013545d71c5f754446->leave($__internal_e4f684ed2462fac4cd5575013545d71c5f754446_prof);
}
if (isset($__internal_d0ad88d8acf2d663bcedeed683cadfd106fb1a4d)) {
$__internal_d0ad88d8acf2d663bcedeed683cadfd106fb1a4d->leave($__internal_d0ad88d8acf2d663bcedeed683cadfd106fb1a4d_prof);
}
}
public function getTemplateName()
{
return 'views/test.pug';
}
public function isTraitable()
{
return false;
}
public function getDebugInfo()
{
return array (
31 => 1,
586 => 2,
);
}
public function getSourceContext()
{
return new Source('{{source}}', 'views/test.pug', '{{path}}');
}
}
Thank you. It's a incompatibility with js-phpize/js-phpize >= 2.8.0. I will fix that, meanwhile I will lock "js-phpize/js-phpize": "~2.7.1"
in the pug-symfony package, thus you can already do in your app package to work around the bug.
I fixed it in js-phpize/js-phpize
2.8.4 so you can now remove the lock from your composer.json and simply re-run composer update
Have a nice week!
Thank you very much!