pug-php / pug-symfony

Pug (Jade) template engine for Symfony

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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)
commented

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}}');
    }
}
commented

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.

commented

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!