yf_tpl_driver_twig classTwig 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.
| File | plugins/tpl/classes/tpl/yf_tpl_driver_twig.class.php |
|---|---|
| Use | _class('tpl_driver_twig', 'classes/tpl/') |
A site sets them in its config with $PROJECT_CONF['tpl_driver_twig']['NAME'] = ...;
| Property | Default | Description |
|---|---|---|
$_yf_filesarray |
[] |
name => source (null: not found) of yf template files looked up by the loader in this request |
$env |
null |
|
$stringsarray |
[] |
name => source of templates parsed with the 'string' param, for include / extends by name |
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();
}
}
_ hooks and helpers; not routable as page actionspublic function __call($name, $arguments)
Catch missing method call.
public function __call($name, $arguments)
{
trigger_error(__CLASS__ . ': No method ' . $name, E_USER_WARNING);
return false;
}
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);
}
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;
}
};
}
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;
}
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];
}