yf_tpl_driver_twig class

Twig 3 template driver: tpl()->parse('twig:name'), ['driver' => 'twig'] or DRIVER_NAME = 'twig'. Twig comes from require_php_lib('twig') (services/tpls/twig.php, locked in services/twig.lock.json). {% include %} / {% extends %} / {% embed %} names resolve on the filesystem paths of _paths() first, then as yf template names (tpl()->get()) or templates parsed earlier by this driver with the 'string' param.

Fileplugins/tpl/classes/tpl/yf_tpl_driver_twig.class.php
Use_class('tpl_driver_twig', 'classes/tpl/')

Public properties

A site sets them in its config with $PROJECT_CONF['tpl_driver_twig']['NAME'] = ...;

PropertyDefaultDescription
$_yf_files
array
[] name => source (null: not found) of yf template files looked up by the loader in this request
$env null
$strings
array
[] name => source of templates parsed with the 'string' param, for include / extends by name

Public methods 6

parse

public function parse($name, $replace = [], $params = [])
    public function parse($name, $replace = [], $params = [])
    {
        if ( ! empty($params['string'])) {
            $s = $params['string'];
            $this->strings[strtolower(trim((string) $name))] = $s;
        } else {
            $s = tpl()->get($name);
        }
        if (empty($params['no_cache'])) {
            return $this->env->createTemplate((string) $s, $name)->render((array) $replace);
        }
        // for this call only: no compiled cache
        $cache = $this->env->getCache(false);
        $auto_reload = $this->env->isAutoReload();
        $this->env->enableAutoReload();
        $this->env->setCache(false);
        try {
            return $this->env->createTemplate((string) $s, $name)->render((array) $replace);
        } finally {
            $this->env->setCache($cache);
            $auto_reload || $this->env->disableAutoReload();
        }
    }

Methods starting with _ hooks and helpers; not routable as page actions

__call

public function __call($name, $arguments)

Catch missing method call.

    public function __call($name, $arguments)
    {
        trigger_error(__CLASS__ . ': No method ' . $name, E_USER_WARNING);
        return false;
    }

_init

public function _init()
    public function _init()
    {
        require_php_lib('twig');

        // path
        $paths = $this->_paths();
        // a site's own composer Twig 2 cannot host the typed loader subclass: plain FilesystemLoader as before
        $loader = \Twig\Environment::MAJOR_VERSION >= 3 ? $this->_loader($paths) : new \Twig\Loader\FilesystemLoader($paths, APP_PATH);
        // env
        if (is_dev() || is_debug()) {
            $debug = true;
            $cache = false;
            $auto_reload = true;
        } else {
            $debug = false;
            $cache = STORAGE_PATH . 'twig_cache/';
            $auto_reload = false;
        }
        $env = [
            'debug'       => $debug,
            'cache'       => $cache,
            'auto_reload' => $auto_reload,
            // 'autoescape'  => false, // name, html, js, css, url, html_attr, ...
        ];
        $this->env = new \Twig\Environment($loader, $env);
        $this->env->addExtension(new \Twig\Extension\StringLoaderExtension());
        if (is_dev() || is_debug()) {
            $this->env->addExtension(new \Twig\Extension\DebugExtension());
        }
        // exec
        $exec = new \Twig\TwigFunction(
            'exec',
            function (\Twig\Environment $env, $context, array $vars = []) {
                if (empty($vars) || ! is_array($vars)) {
                    return;
                }
                $class   = array_shift($vars);
                $_method = array_shift($vars);
                $_class = module_safe($class);
                $_status = method_exists($_class, $_method);
                if ( ! $_status) {
                    $_class = _class_safe($class);
                    $_status = method_exists($_class, $_method);
                    if ( ! $_status) {
                        return;
                    }
                }
                return $_class->$_method(...$vars);
            },
            [
                'is_safe'           => ['html'],
                'needs_context'     => true,
                'needs_environment' => true,
                'is_variadic'       => true,
            ]
        );
        $this->env->addFunction($exec);
    }

_loader

public function _loader($paths)

FilesystemLoader (sites may call addPath() on it) that falls back to yf template names.

    public function _loader($paths)
    {
        return new class($paths, APP_PATH, $this) extends \Twig\Loader\FilesystemLoader {
            private $driver;

            public function __construct($paths, $root, $driver)
            {
                parent::__construct($paths, $root);
                $this->driver = $driver;
            }

            public function getSourceContext(string $name): \Twig\Source
            {
                if (parent::exists($name)) {
                    return parent::getSourceContext($name);
                }
                $source = $this->driver->_yf_source($name);
                if ($source === null) {
                    throw new \Twig\Error\LoaderError('Unable to find template "' . $name . '" (twig paths and yf templates).');
                }
                return new \Twig\Source($source, $name);
            }

            public function getCacheKey(string $name): string
            {
                if (parent::exists($name)) {
                    return parent::getCacheKey($name);
                }
                return 'yf:' . $name . ':' . md5((string) $this->driver->_yf_source($name));
            }

            public function exists(string $name): bool
            {
                return parent::exists($name) || $this->driver->_yf_source($name) !== null;
            }

            public function isFresh(string $name, int $time): bool
            {
                // the yf cache key carries the source hash, a changed source is a new cache entry
                return parent::exists($name) ? parent::isFresh($name, $time) : true;
            }
        };
    }

_paths

public function _paths()
    public function _paths()
    {
        $paths = [
            '.',
            'plugins',
        ];
        // theme, user/admin
        $theme = tpl()->_THEMES_PATH;
        $theme = trim($theme, '/');
        $type = main()->type == 'admin' ? 'admin' : tpl()->_get_def_user_theme();
        // object
        // only route-shaped values become path segments
        $segment = fn ($v) => is_string($v) && preg_match('~^[a-z0-9_]+$~iD', $v) ? $v : '';
        $object = $segment($_GET['object'] ?? '');
        if ($object) {
            $p = ['plugins', $object];
            $paths[] = $p;
            if ( ! empty($theme)) {
                $p[] = $theme;
                $paths[] = $p;
            }
            if ( ! empty($type)) {
                $p[] = $type;
                $paths[] = $p;
                $p[] = $object;
                $paths[] = $p;
            }
            $action = $segment($_GET['action'] ?? '');
            if ($action) {
                $p[] = $action;
                $paths[] = $p;
            }
        }
        // test
        $r = [];
        foreach ($paths as $p) {
            ! is_array($p) && $p = [(string) $p];
            $p = implode(DIRECTORY_SEPARATOR, $p) . DIRECTORY_SEPARATOR;
            is_dir(APP_PATH . $p) && $r[] = $p;
            if (SITE_PATH != APP_PATH) {
                is_dir(SITE_PATH . $p) && $r[] = SITE_PATH . $p;
            }
        }
        $all_tpls_paths = tpl()->_get_cached_paths();
        $paths = [];
        foreach ($all_tpls_paths as $name => $storage) {
            foreach ($storage as $s_type => $f_type) {
                if ( ! in_array($s_type, ['app', 'site'])) {
                    continue;
                }
                if ( ! empty($f_type[$type])) {
                    $d = &$f_type[$type];
                    $c = substr_count($name, DIRECTORY_SEPARATOR);
                    for ($i = 0; $i < $c + 1; $i++) {
                        $d = dirname($d) . DIRECTORY_SEPARATOR;
                        $paths[$d] = true;
                    }
                }
            }
        }
        foreach ($paths as $p => $_) {
            $r[] = $p;
        }
        $r = array_reverse($r);
        return  $r;
    }

_yf_source

public function _yf_source($name)

Source of a yf template name: one parsed earlier with the 'string' param, else tpl()->get().

Returns null|string

    public function _yf_source($name)
    {
        $name = strtolower(trim((string) $name));
        if (isset($this->strings[$name])) {
            return $this->strings[$name];
        }
        if ( ! array_key_exists($name, $this->_yf_files)) {
            // probe first: a miss is the loader's answer (ignore missing, include lists), not a "template not found" warning
            $s = tpl()->get($name, '', true) ? tpl()->get($name) : null;
            $this->_yf_files[$name] = is_string($s) ? $s : null;
        }
        return $this->_yf_files[$name];
    }