mirror of
https://github.com/fenom-template/fenom.git
synced 2023-08-10 21:13:07 +03:00
Dev, dev and dev. Also, docs, docs and docs.
This commit is contained in:
400
src/Aspect.php
400
src/Aspect.php
@ -1,10 +1,12 @@
|
||||
<?php
|
||||
use Aspect\Template;
|
||||
use Aspect\Template,
|
||||
Aspect\ProviderInterface;
|
||||
|
||||
/**
|
||||
* Templater
|
||||
* Aspect Template Engine
|
||||
*/
|
||||
class Aspect {
|
||||
const VERSION = 1.0;
|
||||
|
||||
const INLINE_COMPILER = 1;
|
||||
const BLOCK_COMPILER = 2;
|
||||
@ -12,48 +14,55 @@ class Aspect {
|
||||
const BLOCK_FUNCTION = 4;
|
||||
const MODIFIER = 5;
|
||||
|
||||
const DENY_METHODS = 128;
|
||||
const DENY_INLINE_FUNCS = 256;
|
||||
const DENY_SET_VARS = 512;
|
||||
const DENY_METHODS = 0x10;
|
||||
const DENY_INLINE_FUNCS = 0x20;
|
||||
const FORCE_INCLUDE = 0x40;
|
||||
|
||||
const INCLUDE_SOURCES = 1024;
|
||||
const CHECK_MTIME = 0x80;
|
||||
const FORCE_COMPILE = 0xF0;
|
||||
|
||||
const CHECK_MTIME = 2048;
|
||||
const FORCE_COMPILE = 4096;
|
||||
const DEFAULT_CLOSE_COMPILER = 'Aspect\Compiler::stdClose';
|
||||
const DEFAULT_FUNC_PARSER = 'Aspect\Compiler::stdFuncParser';
|
||||
const DEFAULT_FUNC_OPEN = 'Aspect\Compiler::stdFuncOpen';
|
||||
const DEFAULT_FUNC_CLOSE = 'Aspect\Compiler::stdFuncOpen';
|
||||
const SMART_FUNC_PARSER = 'Aspect\Compiler::smartFuncParser';
|
||||
|
||||
/**
|
||||
* @var array list of possible options, as associative array
|
||||
* @var array of possible options, as associative array
|
||||
* @see setOptions, addOptions, delOptions
|
||||
*/
|
||||
private static $_option_list = array(
|
||||
"disable_methods" => self::DENY_METHODS,
|
||||
"disable_native_funcs" => self::DENY_INLINE_FUNCS,
|
||||
"disable_set_vars" => self::DENY_SET_VARS,
|
||||
"include_sources" => self::INCLUDE_SOURCES,
|
||||
"force_compile" => self::FORCE_COMPILE,
|
||||
"compile_check" => self::CHECK_MTIME,
|
||||
"force_include" => self::FORCE_INCLUDE,
|
||||
);
|
||||
|
||||
/**
|
||||
* Default options for functions
|
||||
* @var array
|
||||
*/
|
||||
private static $_actions_defaults = array(
|
||||
self::BLOCK_FUNCTION => array(
|
||||
'type' => self::BLOCK_FUNCTION,
|
||||
'open' => 'MF\Aspect\Compiler::stdFuncOpen',
|
||||
'close' => 'MF\Aspect\Compiler::stdFuncClose',
|
||||
'open' => self::DEFAULT_FUNC_OPEN,
|
||||
'close' => self::DEFAULT_FUNC_CLOSE,
|
||||
'function' => null,
|
||||
),
|
||||
self::INLINE_FUNCTION => array(
|
||||
'type' => self::INLINE_FUNCTION,
|
||||
'parser' => 'MF\Aspect\Compiler::stdFuncParser',
|
||||
'parser' => self::DEFAULT_FUNC_PARSER,
|
||||
'function' => null,
|
||||
),
|
||||
self::INLINE_FUNCTION => array(
|
||||
self::INLINE_COMPILER => array(
|
||||
'type' => self::INLINE_COMPILER,
|
||||
'open' => null,
|
||||
'close' => 'MF\Aspect\Compiler::stdClose',
|
||||
'close' => self::DEFAULT_CLOSE_COMPILER,
|
||||
'tags' => array(),
|
||||
'float_tags' => array()
|
||||
),
|
||||
self::BLOCK_FUNCTION => array(
|
||||
self::BLOCK_COMPILER => array(
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => null,
|
||||
'close' => null,
|
||||
@ -62,7 +71,6 @@ class Aspect {
|
||||
)
|
||||
);
|
||||
|
||||
public $blocks = array();
|
||||
/**
|
||||
* @var array Templates storage
|
||||
*/
|
||||
@ -81,49 +89,50 @@ class Aspect {
|
||||
*/
|
||||
protected $_options = 0;
|
||||
|
||||
/**
|
||||
* Modifiers loader
|
||||
* @var callable
|
||||
*/
|
||||
protected $_loader_mod;
|
||||
/**
|
||||
* Functions loader
|
||||
* @var callable
|
||||
*/
|
||||
protected $_loader_func;
|
||||
protected $_on_pre_cmp = array();
|
||||
protected $_on_cmp = array();
|
||||
protected $_on_post_cmp = array();
|
||||
|
||||
/**
|
||||
* @var Aspect\Provider
|
||||
*/
|
||||
private $_provider;
|
||||
/**
|
||||
* @var array of Aspect\ProviderInterface
|
||||
*/
|
||||
protected $_providers = array();
|
||||
|
||||
/**
|
||||
* @var array list of modifiers
|
||||
* @var array of modifiers [modifier_name => callable]
|
||||
*/
|
||||
protected $_modifiers = array(
|
||||
"upper" => 'strtoupper',
|
||||
"lower" => 'strtolower',
|
||||
"nl2br" => 'nl2br',
|
||||
"date_format" => 'Aspect\Modifier::dateFormat',
|
||||
"date" => 'Aspect\Modifier::date',
|
||||
"truncate" => 'Aspect\Modifier::truncate',
|
||||
"escape" => 'Aspect\Modifier::escape',
|
||||
"e" => 'Aspect\Modifier::escape', // alias of escape
|
||||
"url" => 'urlencode', // alias of escape:"url"
|
||||
"unescape" => 'Aspect\Modifier::unescape',
|
||||
"strip_tags" => 'strip_tags',
|
||||
"strip" => 'Aspect\Modifier::strip',
|
||||
"default" => 'Aspect\Modifier::defaultValue',
|
||||
"isset" => 'isset',
|
||||
"empty" => 'empty'
|
||||
"default" => 'Aspect\Modifier::defaultValue'
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array list of allowed PHP functions
|
||||
* @var array of allowed PHP functions
|
||||
*/
|
||||
protected $_allowed_funcs = array(
|
||||
"empty" => 1, "isset" => 1, "count" => 1, "is_string" => 1, "is_array" => 1, "is_numeric" => 1, "is_int" => 1, "is_object" => 1
|
||||
"empty" => 1, "isset" => 1, "count" => 1, "is_string" => 1, "is_array" => 1, "is_numeric" => 1, "is_int" => 1,
|
||||
"is_object" => 1, "strtotime" => 1, "gettype" => 1, "is_double" => 1, "json_encode" => 1, "json_decode" => 1,
|
||||
"ip2long" => 1, "long2ip" => 1, "strip_tags" => 1, "nl2br" => 1
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array list of compilers and functions
|
||||
* @var array of compilers and functions
|
||||
*/
|
||||
protected $_actions = array(
|
||||
'foreach' => array(
|
||||
'foreach' => array( // {foreach ...} {break} {continue} {foreachelse} {/foreach}
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => 'Aspect\Compiler::foreachOpen',
|
||||
'close' => 'Aspect\Compiler::foreachClose',
|
||||
@ -134,7 +143,7 @@ class Aspect {
|
||||
),
|
||||
'float_tags' => array('break' => 1, 'continue' => 1)
|
||||
),
|
||||
'if' => array(
|
||||
'if' => array( // {if ...} {elseif ...} {else} {/if}
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => 'Aspect\Compiler::ifOpen',
|
||||
'close' => 'Aspect\Compiler::stdClose',
|
||||
@ -143,7 +152,7 @@ class Aspect {
|
||||
'else' => 'Aspect\Compiler::tagElse',
|
||||
)
|
||||
),
|
||||
'switch' => array(
|
||||
'switch' => array( // {switch ...} {case ...} {break} {default} {/switch}
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => 'Aspect\Compiler::switchOpen',
|
||||
'close' => 'Aspect\Compiler::stdClose',
|
||||
@ -154,7 +163,7 @@ class Aspect {
|
||||
),
|
||||
'float_tags' => array('break' => 1)
|
||||
),
|
||||
'for' => array(
|
||||
'for' => array( // {for ...} {break} {continue} {/for}
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => 'Aspect\Compiler::forOpen',
|
||||
'close' => 'Aspect\Compiler::forClose',
|
||||
@ -165,7 +174,7 @@ class Aspect {
|
||||
),
|
||||
'float_tags' => array('break' => 1, 'continue' => 1)
|
||||
),
|
||||
'while' => array(
|
||||
'while' => array( // {while ...} {break} {continue} {/while}
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => 'Aspect\Compiler::whileOpen',
|
||||
'close' => 'Aspect\Compiler::stdClose',
|
||||
@ -175,24 +184,24 @@ class Aspect {
|
||||
),
|
||||
'float_tags' => array('break' => 1, 'continue' => 1)
|
||||
),
|
||||
'include' => array(
|
||||
'include' => array( // {include ...}
|
||||
'type' => self::INLINE_COMPILER,
|
||||
'parser' => 'Aspect\Compiler::tagInclude'
|
||||
),
|
||||
'var' => array(
|
||||
'var' => array( // {var ...}
|
||||
'type' => self::INLINE_COMPILER,
|
||||
'parser' => 'Aspect\Compiler::assign'
|
||||
),
|
||||
'block' => array(
|
||||
'block' => array( // {block ...} {/block}
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => 'Aspect\Compiler::tagBlockOpen',
|
||||
'close' => 'Aspect\Compiler::tagBlockClose',
|
||||
),
|
||||
'extends' => array(
|
||||
'extends' => array( // {extends ...}
|
||||
'type' => self::INLINE_COMPILER,
|
||||
'parser' => 'Aspect\Compiler::tagExtends'
|
||||
),
|
||||
'capture' => array(
|
||||
'capture' => array( // {capture ...} {/capture}
|
||||
'type' => self::BLOCK_FUNCTION,
|
||||
'open' => 'Aspect\Compiler::stdFuncOpen',
|
||||
'close' => 'Aspect\Compiler::stdFuncClose',
|
||||
@ -206,8 +215,16 @@ class Aspect {
|
||||
|
||||
);
|
||||
|
||||
public static function factory($template_dir, $compile_dir, $options = 0) {
|
||||
$aspect = new static();
|
||||
/**
|
||||
* Factory
|
||||
* @param string $template_dir path to templates
|
||||
* @param string $compile_dir path to compiled files
|
||||
* @param int $options
|
||||
* @param \Aspect\Provider $provider
|
||||
* @return Aspect
|
||||
*/
|
||||
public static function factory($template_dir, $compile_dir, $options = 0, Aspect\Provider $provider = null) {
|
||||
$aspect = new static($provider);
|
||||
$aspect->setCompileDir($compile_dir);
|
||||
$aspect->setTemplateDirs($template_dir);
|
||||
if($options) {
|
||||
@ -216,34 +233,76 @@ class Aspect {
|
||||
return $aspect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Aspect\Provider $provider
|
||||
*/
|
||||
public function __construct(Aspect\Provider $provider = null) {
|
||||
$this->_provider = $provider ?: new Aspect\Provider();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set checks template for modifications
|
||||
* @param $state
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setCompileCheck($state) {
|
||||
$state && ($this->_options |= self::CHECK_MTIME);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set force template compiling
|
||||
* @param $state
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setForceCompile($state) {
|
||||
$state && ($this->_options |= self::FORCE_COMPILE);
|
||||
$this->_storage = $state ? new Aspect\BlackHole() : array();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set compile directory
|
||||
* @param string $dir directory to store compiled templates in
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setCompileDir($dir) {
|
||||
$this->_compile_dir = $dir;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set template directory
|
||||
* @param string|array $dirs directory(s) of template sources
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setTemplateDirs($dirs) {
|
||||
$this->_tpl_path = (array)$dirs;
|
||||
$this->_provider->setTemplateDirs($dirs);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/*public function addPostCompileFilter($cb) {
|
||||
$this->_post_cmp[] = $cb;
|
||||
/**
|
||||
*
|
||||
* @param callable $cb
|
||||
*/
|
||||
public function addPreCompileFilter($cb) {
|
||||
$this->_on_pre_cmp[] = $cb;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param callable $cb
|
||||
*/
|
||||
public function addPostCompileFilter($cb) {
|
||||
$this->_on_post_cmp[] = $cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $cb
|
||||
*/
|
||||
public function addCompileFilter($cb) {
|
||||
$this->_cmp[] = $cb;
|
||||
}*/
|
||||
$this->_on_cmp[] = $cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add modifier
|
||||
@ -252,17 +311,17 @@ class Aspect {
|
||||
* @param string $callback
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setModifier($modifier, $callback) {
|
||||
public function addModifier($modifier, $callback) {
|
||||
$this->_modifiers[$modifier] = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $compiler
|
||||
* @param $parser
|
||||
* @param string $compiler
|
||||
* @param string $parser
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setCompiler($compiler, $parser) {
|
||||
public function addCompiler($compiler, $parser) {
|
||||
$this->_actions[$compiler] = array(
|
||||
'type' => self::INLINE_COMPILER,
|
||||
'parser' => $parser
|
||||
@ -271,31 +330,46 @@ class Aspect {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $compiler
|
||||
* @param array $parsers
|
||||
* @param string $compiler
|
||||
* @param string $open_parser
|
||||
* @param string $close_parser
|
||||
* @param array $tags
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setBlockCompiler($compiler, array $parsers, array $tags = array()) {
|
||||
public function addBlockCompiler($compiler, $open_parser, $close_parser = self::DEFAULT_CLOSE_COMPILER, array $tags = array()) {
|
||||
$this->_actions[$compiler] = array(
|
||||
'type' => self::BLOCK_COMPILER,
|
||||
'open' => $parsers["open"],
|
||||
'close' => isset($parsers["close"]) ? $parsers["close"] : 'Aspect\Compiler::stdClose',
|
||||
'open' => $open_parser,
|
||||
'close' => $close_parser ?: self::DEFAULT_CLOSE_COMPILER,
|
||||
'tags' => $tags,
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $function
|
||||
* @param $callback
|
||||
* @param null $parser
|
||||
* @param string $function
|
||||
* @param callable $callback
|
||||
* @param string $parser
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setFunction($function, $callback, $parser = null) {
|
||||
public function addFunction($function, $callback, $parser = self::DEFAULT_FUNC_PARSER) {
|
||||
$this->_actions[$function] = array(
|
||||
'type' => self::INLINE_FUNCTION,
|
||||
'parser' => $parser ?: 'Aspect\Compiler::stdFuncParser',
|
||||
'parser' => $parser ?: self::DEFAULT_FUNC_PARSER,
|
||||
'function' => $callback,
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $function
|
||||
* @param callable $callback
|
||||
* @return Aspect
|
||||
*/
|
||||
public function addFunctionSmart($function, $callback) {
|
||||
$this->_actions[$function] = array(
|
||||
'type' => self::INLINE_FUNCTION,
|
||||
'parser' => self::SMART_FUNC_PARSER,
|
||||
'function' => $callback,
|
||||
);
|
||||
return $this;
|
||||
@ -308,7 +382,7 @@ class Aspect {
|
||||
* @param null $parser_close
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setBlockFunction($function, $callback, $parser_open = null, $parser_close = null) {
|
||||
public function addBlockFunction($function, $callback, $parser_open = null, $parser_close = null) {
|
||||
$this->_actions[$function] = array(
|
||||
'type' => self::BLOCK_FUNCTION,
|
||||
'open' => $parser_open ?: 'Aspect\Compiler::stdFuncOpen',
|
||||
@ -322,29 +396,11 @@ class Aspect {
|
||||
* @param array $funcs
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setAllowedFunctions(array $funcs) {
|
||||
public function addAllowedFunctions(array $funcs) {
|
||||
$this->_allowed_funcs = $this->_allowed_funcs + array_flip($funcs);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $callback
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setFunctionsLoader($callback) {
|
||||
$this->_loader_func = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $callback
|
||||
* @return Aspect
|
||||
*/
|
||||
public function setModifiersLoader($callback) {
|
||||
$this->_loader_mod = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $modifier
|
||||
* @return mixed
|
||||
@ -355,8 +411,6 @@ class Aspect {
|
||||
return $this->_modifiers[$modifier];
|
||||
} elseif($this->isAllowedFunction($modifier)) {
|
||||
return $modifier;
|
||||
} elseif($this->_loader_mod && $this->_loadModifier($modifier)) {
|
||||
return $this->_modifiers[$modifier];
|
||||
} else {
|
||||
throw new \Exception("Modifier $modifier not found");
|
||||
}
|
||||
@ -369,29 +423,6 @@ class Aspect {
|
||||
public function getFunction($function) {
|
||||
if(isset($this->_actions[$function])) {
|
||||
return $this->_actions[$function];
|
||||
} elseif($this->_loader_func && $this->_loadFunction($function)) {
|
||||
return $this->_actions[$function];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function _loadModifier($modifier) {
|
||||
$mod = call_user_func($this->_loader_mod, $modifier);
|
||||
if($mod) {
|
||||
$this->_modifiers[$modifier] = $mod;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function _loadFunction($function) {
|
||||
$func = call_user_func($this->_loader_func, $function);
|
||||
if($func && isset(self::$_actions_defaults[ $func["type"] ])) {
|
||||
|
||||
$this->_actions[$function] = $func + self::$_actions_defaults[ $func["type"] ];
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -419,23 +450,23 @@ class Aspect {
|
||||
* Add template directory
|
||||
* @static
|
||||
* @param string $dir
|
||||
* @return \Aspect
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addTemplateDir($dir) {
|
||||
$_dir = realpath($dir);
|
||||
if(!$_dir) {
|
||||
throw new \InvalidArgumentException("Invalid template dir: $dir");
|
||||
}
|
||||
$this->_tpl_path[] = $_dir;
|
||||
$this->_provider->addTemplateDir($dir);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addProvider($scm, \Aspect\Provider $provider) {
|
||||
$this->_providers[$scm] = $provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set options. May be bitwise mask of constants DENY_METHODS, DENY_INLINE_FUNCS, DENY_SET_VARS, INCLUDE_SOURCES,
|
||||
* FORCE_COMPILE, CHECK_MTIME, or associative array with boolean values:
|
||||
* disable_methods - disable all call method in template
|
||||
* disable_methods - disable all calls method in template
|
||||
* disable_native_funcs - disable all native PHP functions in template
|
||||
* disable_set_vars - forbidden rewrite variables
|
||||
* include_sources - insert comments with source code into compiled template
|
||||
* force_compile - recompile template every time (very slow!)
|
||||
* compile_check - check template modifications (slow!)
|
||||
* @param int|array $options
|
||||
@ -456,6 +487,23 @@ class Aspect {
|
||||
return $this->_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool|string $scm
|
||||
* @return Aspect\Provider
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function getProvider($scm = false) {
|
||||
if($scm) {
|
||||
if(isset($this->_provider[$scm])) {
|
||||
return $this->_provider[$scm];
|
||||
} else {
|
||||
throw new InvalidArgumentException("Provider for '$scm' not found");
|
||||
}
|
||||
} else {
|
||||
return $this->_provider;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute template and write result into stdout
|
||||
*
|
||||
@ -486,8 +534,11 @@ class Aspect {
|
||||
* @return Aspect\Template
|
||||
*/
|
||||
public function getTemplate($template) {
|
||||
|
||||
if(isset($this->_storage[ $template ])) {
|
||||
if(($this->_options & self::CHECK_MTIME) && !$this->_check($template)) {
|
||||
/** @var Aspect\Template $tpl */
|
||||
$tpl = $this->_storage[ $template ];
|
||||
if(($this->_options & self::CHECK_MTIME) && !$tpl->isValid()) {
|
||||
return $this->_storage[ $template ] = $this->compile($template);
|
||||
} else {
|
||||
return $this->_storage[ $template ];
|
||||
@ -503,7 +554,7 @@ class Aspect {
|
||||
* Add custom template into storage
|
||||
* @param Aspect\Render $template
|
||||
*/
|
||||
public function storeTemplate(Aspect\Render $template) {
|
||||
public function addTemplate(Aspect\Render $template) {
|
||||
$this->_storage[ $template->getName() ] = $template;
|
||||
$template->setStorage($this);
|
||||
}
|
||||
@ -517,7 +568,7 @@ class Aspect {
|
||||
*/
|
||||
protected function _load($tpl) {
|
||||
$file_name = $this->_getHash($tpl);
|
||||
if(!is_file($this->_compile_dir."/".$file_name) || ($this->_options & self::CHECK_MTIME) && !$this->_check($tpl)) {
|
||||
if(!is_file($this->_compile_dir."/".$file_name)) {
|
||||
return $this->compile($tpl);
|
||||
} else {
|
||||
/** @var Aspect\Render $tpl */
|
||||
@ -527,25 +578,6 @@ class Aspect {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $template
|
||||
* @return bool
|
||||
*/
|
||||
private function _check($template) {
|
||||
return $this->_isActual($template, filemtime($this->_compile_dir."/".$this->_getHash($template)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check, if template is actual
|
||||
* @param $template
|
||||
* @param $compiled_time
|
||||
* @return bool
|
||||
*/
|
||||
protected function _isActual($template, $compiled_time) {
|
||||
clearstatcache(false, $template = $this->_getTemplatePath($template));
|
||||
return filemtime($template) < $compiled_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unique name of compiled template
|
||||
*
|
||||
@ -557,45 +589,48 @@ class Aspect {
|
||||
return basename($tpl).".".crc32($hash).".".strlen($hash).".php";
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile and save template
|
||||
*
|
||||
*
|
||||
* @param string $tpl
|
||||
* @throws \RuntimeException
|
||||
* @return \Aspect\Template
|
||||
*/
|
||||
public function compile($tpl) {
|
||||
$file_name = $this->_compile_dir."/".$this->_getHash($tpl);
|
||||
$template = new Template($this, $this->_loadCode($tpl), $tpl);
|
||||
$tpl_tmp = tempnam($this->_compile_dir, basename($tpl));
|
||||
$tpl_fp = fopen($tpl_tmp, "w");
|
||||
if(!$tpl_fp) {
|
||||
throw new \RuntimeException("Can not open temporary file $tpl_tmp. Directory ".$this->_compile_dir." is writable?");
|
||||
}
|
||||
fwrite($tpl_fp, $template->getTemplateCode());
|
||||
fclose($tpl_fp);
|
||||
if(!rename($tpl_tmp, $file_name)) {
|
||||
throw new \RuntimeException("Can not to move $tpl_tmp to $tpl");
|
||||
}
|
||||
/**
|
||||
* Compile and save template
|
||||
*
|
||||
* @param string $tpl
|
||||
* @param bool $store
|
||||
* @throws RuntimeException
|
||||
* @return \Aspect\Template
|
||||
*/
|
||||
public function compile($tpl, $store = true) {
|
||||
$provider = $this->getProvider(strstr($tpl, ":", true));
|
||||
$template = new Template($this, $provider->loadCode($tpl), $tpl);
|
||||
if($store) {
|
||||
$tpl_tmp = tempnam($this->_compile_dir, basename($tpl));
|
||||
$tpl_fp = fopen($tpl_tmp, "w");
|
||||
if(!$tpl_fp) {
|
||||
throw new \RuntimeException("Can not open temporary file $tpl_tmp. Directory ".$this->_compile_dir." is writable?");
|
||||
}
|
||||
fwrite($tpl_fp, $template->getTemplateCode());
|
||||
fclose($tpl_fp);
|
||||
$file_name = $this->_compile_dir."/".$this->_getHash($tpl);
|
||||
if(!rename($tpl_tmp, $file_name)) {
|
||||
throw new \RuntimeException("Can not to move $tpl_tmp to $tpl");
|
||||
}
|
||||
}
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all compiled templates. Warning! Do cleanup the compiled directory.
|
||||
* Remove all compiled templates.
|
||||
*
|
||||
* @param string $scm
|
||||
* @return int
|
||||
* @api
|
||||
*/
|
||||
public function compileAll() {
|
||||
public function compileAll($scm = null) {
|
||||
//return FS::rm($this->_compile_dir.'/*');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tpl
|
||||
* @return bool
|
||||
* @api
|
||||
*/
|
||||
public function clearCompileTemplate($tpl) {
|
||||
public function clearCompiledTemplate($tpl) {
|
||||
$file_name = $this->_compile_dir."/".$this->_getHash($tpl);
|
||||
if(file_exists($file_name)) {
|
||||
return unlink($file_name);
|
||||
@ -606,38 +641,11 @@ class Aspect {
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @api
|
||||
*/
|
||||
public function clearAllCompiles() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template path
|
||||
* @param $tpl
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function _getTemplatePath($tpl) {
|
||||
foreach($this->_tpl_path as $tpl_path) {
|
||||
if(($path = stream_resolve_include_path($tpl_path."/".$tpl)) && strpos($path, $tpl_path) === 0) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
throw new \RuntimeException("Template $tpl not found");
|
||||
}
|
||||
|
||||
/**
|
||||
* Code loader
|
||||
*
|
||||
* @param string $tpl
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function _loadCode(&$tpl) {
|
||||
return file_get_contents($tpl = $this->_getTemplatePath($tpl));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile code to template
|
||||
*
|
||||
|
@ -4,6 +4,9 @@ use Aspect\Tokenizer;
|
||||
use Aspect\Template;
|
||||
use Aspect\Scope;
|
||||
|
||||
/**
|
||||
* Compilers collection
|
||||
*/
|
||||
class Compiler {
|
||||
/**
|
||||
* Tag {include ...}
|
||||
@ -11,7 +14,7 @@ class Compiler {
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Template $tpl
|
||||
* @throws \Exception
|
||||
* @throws ImproperUseException
|
||||
* @return string
|
||||
*/
|
||||
public static function tagInclude(Tokenizer $tokens, Template $tpl) {
|
||||
@ -22,7 +25,7 @@ class Compiler {
|
||||
} elseif (isset($p["file"])) {
|
||||
$file_name = $p["file"];
|
||||
} else {
|
||||
throw new \Exception("{include} require 'file' parameter");
|
||||
throw new ImproperUseException("The tag {include} requires 'file' parameter");
|
||||
}
|
||||
unset($p["file"], $p[0]);
|
||||
if($p) {
|
||||
@ -46,20 +49,18 @@ class Compiler {
|
||||
return 'if('.$scope->tpl->parseExp($tokens, true).') {';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag {elseif ...}
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @throws \Exception
|
||||
* @internal param \Exception $
|
||||
* @return string
|
||||
*/
|
||||
/**
|
||||
* Tag {elseif ...}
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @throws ImproperUseException
|
||||
* @return string
|
||||
*/
|
||||
public static function tagElseIf(Tokenizer $tokens, Scope $scope) {
|
||||
if($scope["else"]) {
|
||||
throw new \Exception('Incorrect use of the tag {else if}');
|
||||
throw new ImproperUseException('Incorrect use of the tag {elseif}');
|
||||
}
|
||||
return '} elseif('.$scope->tpl->parseExp($tokens, true).') {';
|
||||
}
|
||||
@ -85,8 +86,7 @@ class Compiler {
|
||||
* @param Tokenizer $tokens
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @throws \Exception
|
||||
* @internal param \Exception $
|
||||
* @throws ImproperUseException
|
||||
* @return string
|
||||
*/
|
||||
public static function foreachOpen(Tokenizer $tokens, Scope $scope) {
|
||||
@ -102,12 +102,7 @@ class Compiler {
|
||||
$prepend = $uid.' = '.$from.';';
|
||||
$from = $uid;
|
||||
} else {
|
||||
|
||||
if($tokens->valid()) {
|
||||
throw new \Exception("Unexpected token '".$tokens->current()."' in 'foreach'");
|
||||
} else {
|
||||
throw new \Exception("Unexpected end of 'foreach'");
|
||||
}
|
||||
throw new UnexpectedException($tokens, null, "tag {foreach}");
|
||||
}
|
||||
$tokens->get(T_AS);
|
||||
$tokens->next();
|
||||
@ -124,7 +119,7 @@ class Compiler {
|
||||
while($token = $tokens->key()) {
|
||||
$param = $tokens->get(T_STRING);
|
||||
if(!isset($p[ $param ])) {
|
||||
throw new \Exception("Unknown parameter '$param'");
|
||||
throw new ImproperUseException("Unknown parameter '$param' in {foreach}");
|
||||
}
|
||||
$tokens->getNext("=");
|
||||
$tokens->next();
|
||||
@ -166,7 +161,7 @@ class Compiler {
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function foreachElse(Tokenizer $tokens, Scope $scope) {
|
||||
public static function foreachElse($tokens, Scope $scope) {
|
||||
$scope["no-break"] = $scope["no-continue"] = $scope["else"] = true;
|
||||
return " {$scope['after']} } } else {";
|
||||
}
|
||||
@ -179,7 +174,7 @@ class Compiler {
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function foreachClose(Tokenizer $tokens, Scope $scope) {
|
||||
public static function foreachClose($tokens, Scope $scope) {
|
||||
if($scope["else"]) {
|
||||
return '}';
|
||||
} else {
|
||||
@ -192,7 +187,7 @@ class Compiler {
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
* @throws ImproperUseException
|
||||
*/
|
||||
public static function forOpen(Tokenizer $tokens, Scope $scope) {
|
||||
$p = array("index" => false, "first" => false, "last" => false, "step" => 1, "to" => false, "max" => false, "min" => false);
|
||||
@ -213,7 +208,7 @@ class Compiler {
|
||||
$condition = "$var >= {$p['to']}";
|
||||
if($p["last"]) $c = "($var + {$p['step']}) < {$p['to']}";
|
||||
} else {
|
||||
throw new \Exception("Invalid step value");
|
||||
throw new ImproperUseException("Invalid step value if {for}");
|
||||
}
|
||||
} else {
|
||||
$condition = "({$p['step']} > 0 && $var <= {$p['to']} || {$p['step']} < 0 && $var >= {$p['to']})";
|
||||
@ -261,7 +256,7 @@ class Compiler {
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function forClose(Tokenizer $tokens, Scope $scope) {
|
||||
public static function forClose($tokens, Scope $scope) {
|
||||
if($scope["else"]) {
|
||||
return '}';
|
||||
} else {
|
||||
@ -318,14 +313,14 @@ class Compiler {
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @throws \Exception
|
||||
* @throws ImproperUseException
|
||||
* @return string
|
||||
*/
|
||||
public static function tagContinue(Tokenizer $tokens, Scope $scope) {
|
||||
public static function tagContinue($tokens, Scope $scope) {
|
||||
if(empty($scope["no-continue"])) {
|
||||
return 'continue;';
|
||||
} else {
|
||||
throw new \Exception("Incorrect use of the tag {continue}");
|
||||
throw new ImproperUseException("Improper usage of the tag {continue}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -335,7 +330,7 @@ class Compiler {
|
||||
* @static
|
||||
* @return string
|
||||
*/
|
||||
public static function tagDefault(Tokenizer $tokens, Scope $scope) {
|
||||
public static function tagDefault($tokens, Scope $scope) {
|
||||
$code = 'default: ';
|
||||
if($scope["switch"]) {
|
||||
unset($scope["no-break"], $scope["no-continue"]);
|
||||
@ -350,21 +345,38 @@ class Compiler {
|
||||
*
|
||||
* @static
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @throws \Exception
|
||||
* @param Scope $scope
|
||||
* @throws ImproperUseException
|
||||
* @return string
|
||||
*/
|
||||
public static function tagBreak(Tokenizer $tokens, Scope $scope) {
|
||||
public static function tagBreak($tokens, Scope $scope) {
|
||||
if(empty($scope["no-break"])) {
|
||||
return 'break;';
|
||||
} else {
|
||||
throw new \Exception("Incorrect use of the tag {break}");
|
||||
throw new ImproperUseException("Improper usage of the tag {break}");
|
||||
}
|
||||
}
|
||||
|
||||
public static function tagExtends(Tokenizer $tokens, Template $tpl) {
|
||||
/**
|
||||
* check if value is scalar, like "string", 2, 2.2, true, false, null
|
||||
* @param string $value
|
||||
* @return bool
|
||||
* @todo add 'string' support
|
||||
*/
|
||||
public static function isScalar($value) {
|
||||
return json_decode($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch {extends} tag
|
||||
* @param Tokenizer $tokens
|
||||
* @param Template $tpl
|
||||
* @throws ImproperUseException
|
||||
* @return string
|
||||
*/
|
||||
public static function tagExtends(Tokenizer $tokens, Template $tpl) {
|
||||
if(!empty($tpl->_extends)) {
|
||||
throw new \Exception("Only one {extends} allowed");
|
||||
throw new ImproperUseException("Only one {extends} allowed");
|
||||
}
|
||||
$p = $tpl->parseParams($tokens);
|
||||
if(isset($p[0])) {
|
||||
@ -372,17 +384,63 @@ class Compiler {
|
||||
} elseif (isset($p["file"])) {
|
||||
$tpl_name = $p["file"];
|
||||
} else {
|
||||
throw new \Exception("{extends} require 'file' parameter");
|
||||
throw new ImproperUseException("{extends} require 'file' parameter");
|
||||
}
|
||||
$tpl->addPostCompile(__CLASS__."::extendBody");
|
||||
if($name = self::isScalar($tpl_name)) { // static extends
|
||||
$tpl->_extends = $tpl->getStorage()->compile($name, false);
|
||||
$tpl->addDepend($tpl->getStorage()->getTemplate($name)); // for valid compile-time need take template from storage
|
||||
return "/* Static extends */";
|
||||
} else { // dynamic extends
|
||||
$tpl->_extends = $tpl_name;
|
||||
return '/* Dynamic extends */'."\n".'$parent = $tpl->getStorage()->getTemplate('.$tpl_name.');';
|
||||
}
|
||||
$tpl->addPostCompile(__CLASS__."::extendBody");
|
||||
$tpl->_extends = $tpl_name;
|
||||
return '$parent = $tpl->getStorage()->getTemplate('.$tpl_name.');';
|
||||
}
|
||||
|
||||
public static function extendBody(&$body, Template $tpl) {
|
||||
$body = '<?php if(!isset($tpl->blocks)) {$tpl->blocks = array();} ob_start(); ?>'.$body.'<?php ob_end_clean(); $parent->blocks = &$tpl->blocks; $parent->display((array)$tpl); unset($tpl->blocks, $parent->blocks); ?>';
|
||||
/**
|
||||
* Post compile method for {extends ...} tag
|
||||
* @param $body
|
||||
* @param Template $tpl
|
||||
*/
|
||||
public static function extendBody(&$body, $tpl) {
|
||||
if(isset($tpl->_extends)) { // is child
|
||||
if(is_object($tpl->_extends)) { // static extends
|
||||
$t = $tpl;
|
||||
while(isset($t->_extends)) {
|
||||
$t->compile();
|
||||
$t->_blocks += (array)$t->_extends->_blocks;
|
||||
$t = $t->_extends;
|
||||
|
||||
}
|
||||
|
||||
if(empty($t->_blocks)) {
|
||||
$body = $t->getBody();
|
||||
} else {
|
||||
$b = $t->getBody();
|
||||
foreach($t->_blocks as $name => $pos) {
|
||||
|
||||
}
|
||||
}
|
||||
} else { // dynamic extends
|
||||
$body .= '<?php $parent->blocks = &$tpl->blocks; $parent->display((array)$tpl); unset($tpl->blocks, $parent->blocks); ?>';
|
||||
//return '$tpl->blocks['.$scope["name"].'] = ob_get_clean();';
|
||||
}
|
||||
}
|
||||
/*$body = '<?php if(!isset($tpl->blocks)) {$tpl->blocks = array();} ob_start(); ?>'.$body.'<?php ob_end_clean(); $parent->blocks = &$tpl->blocks; $parent->display((array)$tpl); unset($tpl->blocks, $parent->blocks); ?>';*/
|
||||
}
|
||||
|
||||
|
||||
public static function tagUse(Tokenizer $tokens, Template $tpl) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag {block ...}
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
* @throws ImproperUseException
|
||||
*/
|
||||
public static function tagBlockOpen(Tokenizer $tokens, Scope $scope) {
|
||||
$p = $scope->tpl->parseParams($tokens);
|
||||
if(isset($p["name"])) {
|
||||
@ -390,23 +448,63 @@ class Compiler {
|
||||
} elseif (isset($p[0])) {
|
||||
$scope["name"] = $p[0];
|
||||
} else {
|
||||
throw new \Exception("{block} require name parameter");
|
||||
}
|
||||
|
||||
if($scope->closed) {
|
||||
return 'isset($tpl->blocks['.$scope["name"].']) ? $tpl->blocks[] : "" ;';
|
||||
} else {
|
||||
return 'ob_start();';
|
||||
throw new ImproperUseException("{block} must be named");
|
||||
}
|
||||
if(isset($scope->tpl->_extends)) { // is child
|
||||
if(is_object($scope->tpl->_extends)) { // static extends
|
||||
$code = "";
|
||||
} else { // dynamic extends
|
||||
$code = 'if(empty($tpl->blocks['.$scope["name"].'])) { ob_start();';
|
||||
}
|
||||
} else { // is parent
|
||||
if(isset($scope->tpl->_blocks[ $scope["name"] ])) { // skip own block and insert child's block after
|
||||
$scope["body"] = $scope->tpl->_body;
|
||||
$scope->tpl->_body = "";
|
||||
return '';
|
||||
} else {
|
||||
$code = 'if(isset($tpl->blocks['.$scope["name"].'])) { echo $tpl->blocks['.$scope["name"].']; } else {';
|
||||
}
|
||||
}
|
||||
$scope["offset"] = strlen($scope->tpl->getBody()) + strlen($code);
|
||||
return $code;
|
||||
}
|
||||
|
||||
public static function tagBlockClose(Tokenizer $tokens, Scope $scope) {
|
||||
if(isset($scope->tpl->_extends)) {
|
||||
/**
|
||||
* Close tag {/block}
|
||||
* @param Tokenizer $tokens
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function tagBlockClose($tokens, Scope $scope) {
|
||||
$scope->tpl->_blocks[ self::isScalar($scope["name"]) ] = substr($scope->tpl->getBody(), $scope["offset"]);
|
||||
if(isset($scope->tpl->_extends)) { // is child
|
||||
if(is_object($scope->tpl->_extends)) { // static extends
|
||||
return "";
|
||||
} else { // dynamic extends
|
||||
return '$tpl->blocks['.$scope["name"].'] = ob_get_clean(); }';
|
||||
}
|
||||
} else { // is parent
|
||||
if(isset($scope["body"])) {
|
||||
$scope->tpl->_body = $scope["body"].$scope->tpl->_blocks[ $scope["name"] ];
|
||||
return "";
|
||||
} else {
|
||||
return '}';
|
||||
}
|
||||
}
|
||||
/* $scope->tpl->_blocks[ $scope["name"] ] = substr($scope->tpl->getBody(), $scope["offset"]);
|
||||
return '}';*/
|
||||
/*if(isset($scope->tpl->_extends) && is_object($scope->tpl->_extends)) {
|
||||
|
||||
//var_dump("fetched block ".$scope->tpl->_blocks[ $scope["name"] ]);
|
||||
} else {
|
||||
return '}';
|
||||
}*/
|
||||
/*if(isset($scope->tpl->_extends)) {
|
||||
$var = '$i'.$scope->tpl->i++;
|
||||
return $var.' = ob_get_clean(); if('.$var.') $tpl->blocks['.$scope["name"].'] = '.$var.';';
|
||||
} else {
|
||||
return 'if(empty($tpl->blocks['.$scope["name"].'])) { ob_end_flush(); } else { print($tpl->blocks['.$scope["name"].']); ob_end_clean(); }';
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
/**
|
||||
@ -420,7 +518,7 @@ class Compiler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard function tag parser
|
||||
* Standard function parser
|
||||
*
|
||||
* @static
|
||||
* @param $function
|
||||
@ -432,6 +530,35 @@ class Compiler {
|
||||
return "echo $function(".self::_toArray($tpl->parseParams($tokens)).', $tpl);';
|
||||
}
|
||||
|
||||
/**
|
||||
* Smart function parser
|
||||
*
|
||||
* @static
|
||||
* @param $function
|
||||
* @param Tokenizer $tokens
|
||||
* @param Template $tpl
|
||||
* @return string
|
||||
*/
|
||||
public static function smartFuncParser($function, Tokenizer $tokens, Template $tpl) {
|
||||
if(strpos($function, "::")) {
|
||||
$ref = new \ReflectionMethod($function);
|
||||
} else {
|
||||
$ref = new \ReflectionFunction($function);
|
||||
}
|
||||
$args = array();
|
||||
$params = $tpl->parseParams($tokens);
|
||||
foreach($ref->getParameters() as $param) {
|
||||
if(isset($params[ $param->getName() ])) {
|
||||
$args[] = $params[ $param->getName() ];
|
||||
} elseif(isset($params[ $param->getPosition() ])) {
|
||||
$args[] = $params[ $param->getPosition() ];
|
||||
} elseif($param->isOptional()) {
|
||||
$args[] = $param->getDefaultValue();
|
||||
}
|
||||
}
|
||||
return "echo $function(".implode(", ", $args).');';
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard function open tag parser
|
||||
*
|
||||
@ -453,7 +580,7 @@ class Compiler {
|
||||
* @param Scope $scope
|
||||
* @return string
|
||||
*/
|
||||
public static function stdFuncClose(Tokenizer $tokens, Scope $scope) {
|
||||
public static function stdFuncClose($tokens, Scope $scope) {
|
||||
return "echo ".$scope["function"].'('.$scope["params"].', ob_get_clean(), $tpl);';
|
||||
}
|
||||
|
||||
@ -478,6 +605,13 @@ class Compiler {
|
||||
return self::setVar($tokens, $tpl).';';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set variable expression parser
|
||||
* @param Tokenizer $tokens
|
||||
* @param Template $tpl
|
||||
* @param bool $allow_array
|
||||
* @return string
|
||||
*/
|
||||
public static function setVar(Tokenizer $tokens, Template $tpl, $allow_array = true) {
|
||||
$var = $tpl->parseVar($tokens, $tpl::DENY_MODS);
|
||||
|
||||
@ -490,4 +624,13 @@ class Compiler {
|
||||
}
|
||||
}
|
||||
|
||||
public static function tagModifyOpen(Tokenizer $tokens, Scope $scope) {
|
||||
$scope["modifiers"] = $scope->tpl->parseModifier($tokens, "ob_get_clean()");
|
||||
return "ob_start();";
|
||||
}
|
||||
|
||||
public static function tagModifyClose($tokens, Scope $scope) {
|
||||
return "echo ".$scope["modifiers"].";";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aspect;
|
||||
|
||||
|
||||
class Misc {
|
||||
|
||||
/**
|
||||
|
78
src/Aspect/Provider.php
Normal file
78
src/Aspect/Provider.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
namespace Aspect;
|
||||
/**
|
||||
* Templates provider
|
||||
* @author Ivan Shalganov
|
||||
*/
|
||||
class Provider implements ProviderInterface {
|
||||
private $_tpl_path = array();
|
||||
|
||||
public function setTemplateDirs($dirs) {
|
||||
foreach((array)$dirs as $dir) {
|
||||
$this->addTemplateDir($dir);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addTemplateDir($dir) {
|
||||
if($_dir = realpath($dir)) {
|
||||
$this->_tpl_path[] = $_dir;
|
||||
} else {
|
||||
throw new \LogicException("Template directory {$dir} doesn't exists");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tpl
|
||||
* @return string
|
||||
*/
|
||||
public function loadCode($tpl) {
|
||||
return file_get_contents($tpl = $this->_getTemplatePath($tpl));
|
||||
}
|
||||
|
||||
public function getLastModified($tpl) {
|
||||
clearstatcache(null, $tpl = $this->_getTemplatePath($tpl));
|
||||
return filemtime($tpl);
|
||||
}
|
||||
|
||||
public function getAll() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template path
|
||||
* @param $tpl
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function _getTemplatePath($tpl) {
|
||||
foreach($this->_tpl_path as $tpl_path) {
|
||||
if(($path = realpath($tpl_path."/".$tpl)) && strpos($path, $tpl_path) === 0) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
throw new \RuntimeException("Template $tpl not found");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tpl
|
||||
* @return bool
|
||||
*/
|
||||
public function isTemplateExists($tpl) {
|
||||
foreach($this->_tpl_path as $tpl_path) {
|
||||
if(($path = realpath($tpl_path."/".$tpl)) && strpos($path, $tpl_path) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getLastModifiedBatch($tpls) {
|
||||
$tpls = array_flip($tpls);
|
||||
foreach($tpls as $tpl => &$time) {
|
||||
$time = $this->getLastModified($tpl);
|
||||
}
|
||||
return $tpls;
|
||||
}
|
||||
}
|
28
src/Aspect/ProviderInterface.php
Normal file
28
src/Aspect/ProviderInterface.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Aspect;
|
||||
|
||||
interface ProviderInterface {
|
||||
/**
|
||||
* @param string $tpl
|
||||
* @return bool
|
||||
*/
|
||||
public function isTemplateExists($tpl);
|
||||
/**
|
||||
* @param string $tpl
|
||||
* @return string
|
||||
*/
|
||||
public function loadCode($tpl);
|
||||
|
||||
/**
|
||||
* @param string $tpl
|
||||
* @return int
|
||||
*/
|
||||
public function getLastModified($tpl);
|
||||
|
||||
public function getLastModifiedBatch($tpls);
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAll();
|
||||
}
|
@ -20,20 +20,23 @@ class Render extends \ArrayObject {
|
||||
*/
|
||||
protected $_aspect;
|
||||
/**
|
||||
* Signature of the template
|
||||
* @var mixed
|
||||
* Timestamp of compilation
|
||||
* @var float
|
||||
*/
|
||||
protected $_fingerprint;
|
||||
protected $_time = 0.0;
|
||||
|
||||
protected $_depends = array();
|
||||
|
||||
/**
|
||||
* @param string $name template name
|
||||
* @param callable $code template body
|
||||
* @param mixed $fingerprint signature
|
||||
* @param mixed $props signature
|
||||
*/
|
||||
public function __construct($name, \Closure $code, $fingerprint = null) {
|
||||
public function __construct($name, \Closure $code, $props = array()) {
|
||||
$this->_name = $name;
|
||||
$this->_code = $code;
|
||||
$this->_fingerprint = $fingerprint;
|
||||
$this->_time = isset($props["time"]) ? $props["time"] : microtime(true);
|
||||
$this->_depends = isset($props["depends"]) ? $props["depends"] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,17 +70,26 @@ class Render extends \ArrayObject {
|
||||
return $this->_name;
|
||||
}
|
||||
|
||||
public function getCompileTime() {
|
||||
return $this->_time;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate template version
|
||||
* @param mixed $fingerprint of the template
|
||||
* Validate template
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($fingerprint) {
|
||||
if($this->_fingerprint) {
|
||||
return $fingerprint === $this->_fingerprint;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
public function isValid() {
|
||||
$provider = $this->_aspect->getProvider(strstr($this->_name, ":"), true);
|
||||
if($provider->getLastModified($this->_name) >= $this->_time) {
|
||||
return false;
|
||||
}
|
||||
foreach($this->_depends as $tpl => $time) {
|
||||
if($this->_aspect->getTemplate($tpl)->getCompileTime() !== $time) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,7 +19,7 @@ class Template extends Render {
|
||||
* Template PHP code
|
||||
* @var string
|
||||
*/
|
||||
private $_body;
|
||||
public $_body;
|
||||
/**
|
||||
* Call stack
|
||||
* @var Scope[]
|
||||
@ -41,7 +41,7 @@ class Template extends Render {
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $_literal = false;
|
||||
private $_ignore = false;
|
||||
/**
|
||||
* Options
|
||||
* @var int
|
||||
@ -57,45 +57,57 @@ class Template extends Render {
|
||||
* @param Aspect $aspect Template storage
|
||||
* @param string $code template source
|
||||
* @param string $name template name
|
||||
* @throws CompileException
|
||||
* @param bool $auto_compile
|
||||
*/
|
||||
public function __construct(Aspect $aspect, $code, $name = "runtime template") {
|
||||
public function __construct(Aspect $aspect, $code, $name = "runtime template", $auto_compile = true) {
|
||||
$this->_src = $code;
|
||||
$this->_name = $name;
|
||||
$this->_aspect = $aspect;
|
||||
$this->_options = $aspect->getOptions();
|
||||
if($auto_compile) {
|
||||
$this->compile();
|
||||
}
|
||||
}
|
||||
|
||||
public function compile() {
|
||||
if(!isset($this->_src)) {
|
||||
return;
|
||||
}
|
||||
$this->_time = microtime(true);
|
||||
$pos = 0;
|
||||
while(($start = strpos($code, '{', $pos)) !== false) { // search open-char of tags
|
||||
switch($code[$start + 1]) { // check next char
|
||||
while(($start = strpos($this->_src, '{', $pos)) !== false) { // search open-char of tags
|
||||
switch($this->_src[$start + 1]) { // check next char
|
||||
case "\n": case "\r": case "\t": case " ": case "}": // ignore the tag
|
||||
$pos = $start + 1; // trying finding tags after the current char
|
||||
$pos = $start + 1; // try find tags after the current char
|
||||
continue 2;
|
||||
case "*": // if comment block
|
||||
$end = strpos($code, '*}', $start); // finding end of the comment block
|
||||
$frag = substr($code, $this->_pos, $start - $end); // read the comment block for precessing
|
||||
$end = strpos($this->_src, '*}', $start); // finding end of the comment block
|
||||
$frag = substr($this->_src, $this->_pos, $start - $end); // read the comment block for precessing
|
||||
$this->_line += substr_count($frag, "\n"); // count skipped lines
|
||||
$pos = $end + 1; // trying finding tags after the comment block
|
||||
continue 2;
|
||||
}
|
||||
$end = strpos($code, '}', $start); // search close-char of the tag
|
||||
$end = strpos($this->_src, '}', $start); // search close-char of the tag
|
||||
if(!$end) { // if unexpected end of template
|
||||
throw new CompileException("Unclosed tag in line {$this->_line}", 0, 1, $this->_name, $this->_line);
|
||||
}
|
||||
$frag = substr($code, $this->_pos, $start - $this->_pos); // variable $frag contains chars after last '}' and new '{'
|
||||
$tag = substr($code, $start, $end - $start + 1); // variable $tag contains aspect tag '{...}'
|
||||
$this->_line += substr_count($code, "\n", $this->_pos, $end - $start + 1); // count lines in $frag and $tag (using original text $code)
|
||||
$pos = $this->_pos = $end + 1; // move search pointer to end of the tag
|
||||
$frag = substr($this->_src, $this->_pos, $start - $this->_pos); // variable $frag contains chars after last '}' and next '{'
|
||||
$tag = substr($this->_src, $start, $end - $start + 1); // variable $tag contains aspect tag '{...}'
|
||||
$this->_line += substr_count($this->_src, "\n", $this->_pos, $end - $start + 1); // count lines in $frag and $tag (using original text $code)
|
||||
$pos = $this->_pos = $end + 1; // move search-pointer to end of the tag
|
||||
if($this->_trim) { // if previous tag has trim flag
|
||||
$frag = ltrim($frag);
|
||||
}
|
||||
$tag = $this->_tag($tag, $this->_trim);
|
||||
|
||||
$tag = $this->_tag($tag, $this->_trim); // dispatching tags
|
||||
|
||||
if($this->_trim) { // if current tag has trim flag
|
||||
$frag = rtrim($frag);
|
||||
}
|
||||
$this->_body .= $frag.$tag;
|
||||
$this->_body .= str_replace("<?", '<?php echo "<?" ?>', $frag).$tag;
|
||||
|
||||
}
|
||||
$this->_body .= substr($code, $this->_pos);
|
||||
$this->_body .= substr($this->_src, $this->_pos);
|
||||
if($this->_stack) {
|
||||
$_names = array();
|
||||
$_line = 0;
|
||||
@ -105,7 +117,7 @@ class Template extends Render {
|
||||
}
|
||||
$_names[] = $scope->name.' defined on line '.$scope->line;
|
||||
}
|
||||
throw new CompileException("Unclosed tags: ".implode(", ", $_names), 0, 1, $this->_name, $_line);
|
||||
throw new CompileException("Unclosed block tags: ".implode(", ", $_names), 0, 1, $this->_name, $_line);
|
||||
}
|
||||
unset($this->_src);
|
||||
if($this->_post) {
|
||||
@ -128,13 +140,18 @@ class Template extends Render {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return PHP code of PHP file of template
|
||||
* Return PHP code for saving to file
|
||||
* @return string
|
||||
*/
|
||||
public function getTemplateCode() {
|
||||
return "<?php \n".
|
||||
"/** Aspect template '".$this->_name."' compiled at ".date('Y-m-d H:i:s')." */\n".
|
||||
"return new Aspect\\Render('{$this->_name}', ".$this->_getClosureCode().", ".$this->_options.");\n";
|
||||
"return new Aspect\\Render('{$this->_name}', ".$this->_getClosureCode().", ".var_export(array(
|
||||
"options" => $this->_options,
|
||||
//"provider" =>
|
||||
"time" => $this->_time,
|
||||
"depends" => $this->_depends
|
||||
), true).");\n";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -164,6 +181,14 @@ class Template extends Render {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add depends from template
|
||||
* @param Render $tpl
|
||||
*/
|
||||
public function addDepend(Render $tpl) {
|
||||
$this->_depends[$tpl->getName()] = $tpl->getCompileTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute template and return result as string
|
||||
* @param array $values for template
|
||||
@ -198,9 +223,9 @@ class Template extends Render {
|
||||
$trim = false;
|
||||
}
|
||||
$token = trim($token);
|
||||
if($this->_literal) {
|
||||
if($token === '/literal') {
|
||||
$this->_literal = false;
|
||||
if($this->_ignore) {
|
||||
if($token === '/ignore') {
|
||||
$this->_ignore = false;
|
||||
return '';
|
||||
} else {
|
||||
return $src;
|
||||
@ -229,15 +254,17 @@ class Template extends Render {
|
||||
if($tokens->key()) { // if tokenizer still have tokens
|
||||
throw new UnexpectedException($tokens);
|
||||
}
|
||||
if($this->_options & Aspect::INCLUDE_SOURCES) {
|
||||
if(!$code) {
|
||||
return "";
|
||||
} else {
|
||||
return "<?php\n/* {$this->_name}:{$this->_line}: {$src} */\n {$code} ?>";
|
||||
} else {
|
||||
return "<?php {$code} ?>";
|
||||
}
|
||||
} catch (ImproperUseException $e) {
|
||||
throw new CompileException($e->getMessage()." in {$this} line {$this->_line}", 0, E_ERROR, $this->_name, $this->_line, $e);
|
||||
} catch (\LogicException $e) {
|
||||
throw new SecurityException($e->getMessage()." in {$this} line {$this->_line}, near '{".$tokens->getSnippetAsString(0,0)."' <- there", 0, 1, $this->_name, $this->_line, $e);
|
||||
throw new SecurityException($e->getMessage()." in {$this} line {$this->_line}, near '{".$tokens->getSnippetAsString(0,0)."' <- there", 0, E_ERROR, $this->_name, $this->_line, $e);
|
||||
} catch (\Exception $e) {
|
||||
throw new CompileException($e->getMessage()." in {$this} line {$this->_line}, near '{".$tokens->getSnippetAsString(0,0)."' <- there", 0, 1, $this->_name, $this->_line, $e);
|
||||
throw new CompileException($e->getMessage()." in {$this} line {$this->_line}, near '{".$tokens->getSnippetAsString(0,0)."' <- there", 0, E_ERROR, $this->_name, $this->_line, $e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,8 +304,8 @@ class Template extends Render {
|
||||
return 'echo '.$this->parseExp($tokens).';';
|
||||
}
|
||||
|
||||
if($action === "literal") {
|
||||
$this->_literal = true;
|
||||
if($action === "ignore") {
|
||||
$this->_ignore = true;
|
||||
$tokens->next();
|
||||
return '';
|
||||
}
|
||||
@ -409,9 +436,6 @@ class Template extends Render {
|
||||
$_exp .= $tokens->getAndNext();
|
||||
} elseif($term && !$cond && !$tokens->isLast()) {
|
||||
if($tokens->is(Tokenizer::MACRO_EQUALS) && $term === 2) {
|
||||
if($this->_options & Aspect::DENY_SET_VARS) {
|
||||
throw new \LogicException("Forbidden to set a variable");
|
||||
}
|
||||
$_exp .= ' '.$tokens->getAndNext().' ';
|
||||
$term = 0;
|
||||
} else {
|
||||
@ -507,14 +531,34 @@ class Template extends Render {
|
||||
}
|
||||
} elseif($t === T_DNUMBER) {
|
||||
$_var .= '['.substr($tokens->getAndNext(), 1).']';
|
||||
} elseif($t === "?") {
|
||||
} elseif($t === "?" || $t === "!") {
|
||||
$pure_var = false;
|
||||
$empty = ($t === "?");
|
||||
$tokens->next();
|
||||
if($tokens->is(":")) {
|
||||
$tokens->next();
|
||||
return '(empty('.$_var.') ? ('.$this->parseExp($tokens, true).') : '.$_var.')';
|
||||
if($empty) {
|
||||
return '(empty('.$_var.') ? ('.$this->parseExp($tokens, true).') : '.$_var.')';
|
||||
} else {
|
||||
return '(isset('.$_var.') ? '.$_var.' : ('.$this->parseExp($tokens, true).'))';
|
||||
}
|
||||
} elseif($tokens->is(Tokenizer::MACRO_BINARY, Tokenizer::MACRO_BOOLEAN, Tokenizer::MACRO_MATH) || !$tokens->valid()) {
|
||||
if($empty) {
|
||||
return '!empty('.$_var.')';
|
||||
} else {
|
||||
return 'isset('.$_var.')';
|
||||
}
|
||||
} else {
|
||||
return '!empty('.$_var.')';
|
||||
$expr1 = $this->parseExp($tokens, true);
|
||||
if(!$tokens->is(":")) {
|
||||
throw new UnexpectedException($tokens, null, "ternary operator");
|
||||
}
|
||||
$expr2 = $this->parseExp($tokens, true);
|
||||
if($empty) {
|
||||
return '(empty('.$_var.') ? '.$expr2.' : '.$expr1;
|
||||
} else {
|
||||
return '(isset('.$_var.') ? '.$expr1.' : '.$expr2;
|
||||
}
|
||||
}
|
||||
} elseif($t === "!") {
|
||||
$pure_var = false;
|
||||
@ -802,7 +846,7 @@ class Template extends Render {
|
||||
$params[ $key ] = $this->parseExp($tokens);
|
||||
} else {
|
||||
$params[ $key ] = true;
|
||||
$params[] = "'".$key."'";
|
||||
$params[] = '"'.$key.'"';
|
||||
}
|
||||
} elseif($tokens->is(Tokenizer::MACRO_SCALAR, '"', '`', T_VARIABLE, "[", '(')) {
|
||||
$params[] = $this->parseExp($tokens);
|
||||
@ -821,4 +865,5 @@ class Template extends Render {
|
||||
|
||||
|
||||
class CompileException extends \ErrorException {}
|
||||
class SecurityException extends CompileException {}
|
||||
class SecurityException extends CompileException {}
|
||||
class ImproperUseException extends \LogicException {}
|
@ -6,7 +6,6 @@ defined('T_TRAIT') || define('T_TRAIT', 355);
|
||||
defined('T_TRAIT_C') || define('T_TRAIT_C', 365);
|
||||
|
||||
/**
|
||||
* This iterator cannot be rewinded.
|
||||
* Each token have structure
|
||||
* - Token (constant T_* or text)
|
||||
* - Token name (textual representation of the token)
|
||||
@ -28,14 +27,14 @@ class Tokenizer {
|
||||
* Some text value: foo, bar, new, class ...
|
||||
*/
|
||||
const MACRO_STRING = 1000;
|
||||
/**
|
||||
* Unary operation: ~, !, ^
|
||||
*/
|
||||
const MACRO_UNARY = 1001;
|
||||
/**
|
||||
* Binary operation (operation between two values): +, -, *, /, &&, or , ||, >=, !=, ...
|
||||
*/
|
||||
const MACRO_BINARY = 1002;
|
||||
/**
|
||||
* Unary operation: ~, !, ^
|
||||
*/
|
||||
const MACRO_UNARY = 1001;
|
||||
/**
|
||||
* Binary operation (operation between two values): +, -, *, /, &&, or , ||, >=, !=, ...
|
||||
*/
|
||||
const MACRO_BINARY = 1002;
|
||||
/**
|
||||
* Equal operation
|
||||
*/
|
||||
@ -44,14 +43,14 @@ class Tokenizer {
|
||||
* Scalar values (such as int, float, escaped strings): 2, 0.5, "foo", 'bar\'s'
|
||||
*/
|
||||
const MACRO_SCALAR = 1004;
|
||||
/**
|
||||
* Increment or decrement: ++ --
|
||||
*/
|
||||
const MACRO_INCDEC = 1005;
|
||||
/**
|
||||
* Boolean operations: &&, ||, or, xor
|
||||
*/
|
||||
const MACRO_BOOLEAN = 1006;
|
||||
/**
|
||||
* Increment or decrement: ++ --
|
||||
*/
|
||||
const MACRO_INCDEC = 1005;
|
||||
/**
|
||||
* Boolean operations: &&, ||, or, xor
|
||||
*/
|
||||
const MACRO_BOOLEAN = 1006;
|
||||
/**
|
||||
* Math operation
|
||||
*/
|
||||
@ -61,8 +60,8 @@ class Tokenizer {
|
||||
*/
|
||||
const MACRO_COND = 1008;
|
||||
|
||||
public $tokens;
|
||||
public $p = 0;
|
||||
public $tokens;
|
||||
public $p = 0;
|
||||
private $_max = 0;
|
||||
private $_last_no = 0;
|
||||
|
||||
@ -73,45 +72,45 @@ class Tokenizer {
|
||||
private static $_macros = array(
|
||||
self::MACRO_STRING => array(
|
||||
\T_ABSTRACT => 1, \T_ARRAY => 1, \T_AS => 1, \T_BREAK => 1, \T_BREAK => 1, \T_CASE => 1,
|
||||
\T_CATCH => 1, \T_CLASS => 1, \T_CLASS_C => 1, \T_CLONE => 1, \T_CONST => 1, \T_CONTINUE => 1,
|
||||
\T_CATCH => 1, \T_CLASS => 1, \T_CLASS_C => 1, \T_CLONE => 1, \T_CONST => 1, \T_CONTINUE => 1,
|
||||
\T_DECLARE => 1, \T_DEFAULT => 1, \T_DIR => 1, \T_DO => 1, \T_ECHO => 1, \T_ELSE => 1,
|
||||
\T_ELSEIF => 1, \T_EMPTY => 1, \T_ENDDECLARE => 1, \T_ENDFOR => 1, \T_ENDFOREACH => 1, \T_ENDIF => 1,
|
||||
\T_ENDSWITCH => 1, \T_ENDWHILE => 1, \T_EVAL => 1, \T_EXIT => 1, \T_EXTENDS => 1, \T_FILE => 1,
|
||||
\T_FINAL => 1, \T_FOR => 1, \T_FOREACH => 1, \T_FUNCTION => 1, \T_FUNC_C => 1, \T_GLOBAL => 1,
|
||||
\T_GOTO => 1, \T_HALT_COMPILER => 1, \T_IF => 1, \T_IMPLEMENTS => 1, \T_INCLUDE => 1, \T_INCLUDE_ONCE => 1,
|
||||
\T_INSTANCEOF => 1, \T_INSTEADOF => 1, \T_INTERFACE => 1, \T_ISSET => 1, \T_LINE => 1, \T_LIST => 1,
|
||||
\T_LOGICAL_AND => 1, \T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_METHOD_C => 1, \T_NAMESPACE => 1, \T_NS_C => 1,
|
||||
\T_NEW => 1, \T_PRINT => 1, \T_PRIVATE => 1, \T_PUBLIC => 1, \T_PROTECTED => 1, \T_REQUIRE => 1,
|
||||
\T_REQUIRE_ONCE => 1,\T_RETURN => 1, \T_RETURN => 1, \T_STRING => 1, \T_SWITCH => 1, \T_THROW => 1,
|
||||
\T_TRAIT => 1, \T_TRAIT_C => 1, \T_TRY => 1, \T_UNSET => 1, \T_UNSET => 1, \T_VAR => 1,
|
||||
\T_WHILE => 1
|
||||
\T_ELSEIF => 1, \T_EMPTY => 1, \T_ENDDECLARE => 1, \T_ENDFOR => 1, \T_ENDFOREACH => 1, \T_ENDIF => 1,
|
||||
\T_ENDSWITCH => 1, \T_ENDWHILE => 1, \T_EVAL => 1, \T_EXIT => 1, \T_EXTENDS => 1, \T_FILE => 1,
|
||||
\T_FINAL => 1, \T_FOR => 1, \T_FOREACH => 1, \T_FUNCTION => 1, \T_FUNC_C => 1, \T_GLOBAL => 1,
|
||||
\T_GOTO => 1, \T_HALT_COMPILER => 1, \T_IF => 1, \T_IMPLEMENTS => 1, \T_INCLUDE => 1, \T_INCLUDE_ONCE => 1,
|
||||
\T_INSTANCEOF => 1, \T_INSTEADOF => 1, \T_INTERFACE => 1, \T_ISSET => 1, \T_LINE => 1, \T_LIST => 1,
|
||||
\T_LOGICAL_AND => 1, \T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_METHOD_C => 1, \T_NAMESPACE => 1, \T_NS_C => 1,
|
||||
\T_NEW => 1, \T_PRINT => 1, \T_PRIVATE => 1, \T_PUBLIC => 1, \T_PROTECTED => 1, \T_REQUIRE => 1,
|
||||
\T_REQUIRE_ONCE => 1,\T_RETURN => 1, \T_RETURN => 1, \T_STRING => 1, \T_SWITCH => 1, \T_THROW => 1,
|
||||
\T_TRAIT => 1, \T_TRAIT_C => 1, \T_TRY => 1, \T_UNSET => 1, \T_UNSET => 1, \T_VAR => 1,
|
||||
\T_WHILE => 1
|
||||
),
|
||||
self::MACRO_INCDEC => array(
|
||||
\T_INC => 1, \T_DEC => 1
|
||||
),
|
||||
self::MACRO_INCDEC => array(
|
||||
\T_INC => 1, \T_DEC => 1
|
||||
),
|
||||
self::MACRO_UNARY => array(
|
||||
"!" => 1, "~" => 1, "-" => 1
|
||||
),
|
||||
self::MACRO_BINARY => array(
|
||||
\T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1, \T_IS_GREATER_OR_EQUAL => 1, \T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1,
|
||||
\T_IS_NOT_EQUAL => 1,\T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1, \T_LOGICAL_AND => 1,
|
||||
\T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_SL => 1, \T_SR => 1,
|
||||
"+" => 1, "-" => 1, "*" => 1, "/" => 1, ">" => 1, "<" => 1, "^" => 1, "%" => 1, "&" => 1
|
||||
\T_IS_NOT_EQUAL => 1,\T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1, \T_LOGICAL_AND => 1,
|
||||
\T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_SL => 1, \T_SR => 1,
|
||||
"+" => 1, "-" => 1, "*" => 1, "/" => 1, ">" => 1, "<" => 1, "^" => 1, "%" => 1, "&" => 1
|
||||
),
|
||||
self::MACRO_BOOLEAN => array(
|
||||
\T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1
|
||||
),
|
||||
self::MACRO_MATH => array(
|
||||
"+" => 1, "-" => 1, "*" => 1, "/" => 1, "^" => 1, "%" => 1, "&" => 1, "|" => 1
|
||||
),
|
||||
self::MACRO_COND => array(
|
||||
\T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1, ">" => 1, "<" => 1, \T_SL => 1, \T_SR => 1,
|
||||
\T_IS_NOT_EQUAL => 1,\T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1,
|
||||
),
|
||||
self::MACRO_BOOLEAN => array(
|
||||
\T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1
|
||||
),
|
||||
self::MACRO_MATH => array(
|
||||
"+" => 1, "-" => 1, "*" => 1, "/" => 1, "^" => 1, "%" => 1, "&" => 1, "|" => 1
|
||||
),
|
||||
self::MACRO_COND => array(
|
||||
\T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1, ">" => 1, "<" => 1, \T_SL => 1, \T_SR => 1,
|
||||
\T_IS_NOT_EQUAL => 1,\T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1,
|
||||
),
|
||||
self::MACRO_EQUALS => array(
|
||||
\T_AND_EQUAL => 1, \T_CONCAT_EQUAL => 1,\T_DIV_EQUAL => 1, \T_MINUS_EQUAL => 1, \T_MOD_EQUAL => 1,
|
||||
\T_MUL_EQUAL => 1, \T_OR_EQUAL => 1, \T_PLUS_EQUAL => 1, \T_SL_EQUAL => 1, \T_SR_EQUAL => 1,
|
||||
\T_XOR_EQUAL => 1, '=' => 1
|
||||
\T_MUL_EQUAL => 1, \T_OR_EQUAL => 1, \T_PLUS_EQUAL => 1, \T_SL_EQUAL => 1, \T_SR_EQUAL => 1,
|
||||
\T_XOR_EQUAL => 1, '=' => 1
|
||||
),
|
||||
self::MACRO_SCALAR => array(
|
||||
\T_LNUMBER => 1, \T_DNUMBER => 1, \T_CONSTANT_ENCAPSED_STRING => 1
|
||||
@ -165,12 +164,12 @@ class Tokenizer {
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function __construct($query, $decode = 0) {
|
||||
$this->tokens = self::decode($query, $decode);
|
||||
public function __construct($query, $decode = 0) {
|
||||
$this->tokens = self::decode($query, $decode);
|
||||
unset($this->tokens[-1]);
|
||||
$this->_max = count($this->tokens) - 1;
|
||||
$this->_last_no = $this->tokens[$this->_max][3];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the filter callback. Token may be changed by reference or skipped if callback return false.
|
||||
@ -188,15 +187,15 @@ class Tokenizer {
|
||||
$this->_max = count($this->tokens) - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current element
|
||||
*
|
||||
* @link http://php.net/manual/en/iterator.current.php
|
||||
* @return mixed Can return any type.
|
||||
*/
|
||||
public function current() {
|
||||
return $this->curr[1];
|
||||
}
|
||||
/**
|
||||
* Return the current element
|
||||
*
|
||||
* @link http://php.net/manual/en/iterator.current.php
|
||||
* @return mixed Can return any type.
|
||||
*/
|
||||
public function current() {
|
||||
return $this->curr[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Move forward to next element
|
||||
@ -204,14 +203,14 @@ class Tokenizer {
|
||||
* @link http://php.net/manual/en/iterator.next.php
|
||||
* @return Tokenizer
|
||||
*/
|
||||
public function next() {
|
||||
public function next() {
|
||||
if($this->p > $this->_max) {
|
||||
return $this;
|
||||
}
|
||||
$this->p++;
|
||||
$this->p++;
|
||||
unset($this->prev, $this->curr, $this->next);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check token type. If token type is one of expected types return true. Otherwise return false
|
||||
@ -243,7 +242,7 @@ class Tokenizer {
|
||||
* @return mixed
|
||||
*/
|
||||
public function _next($tokens) {
|
||||
$this->next();
|
||||
$this->next();
|
||||
if(!$this->curr) {
|
||||
throw new TokenizeException("Unexpected end of expression");
|
||||
}
|
||||
@ -260,16 +259,16 @@ class Tokenizer {
|
||||
$expect = "";
|
||||
}
|
||||
throw new TokenizeException("Unexpected token '".$this->current()."'$expect");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch next specified token or throw an exception
|
||||
* @return mixed
|
||||
*/
|
||||
public function getNext(/*int|string $token1, int|string $token2, ... */) {
|
||||
$this->_next(func_get_args());
|
||||
return $this->current();
|
||||
}
|
||||
$this->_next(func_get_args());
|
||||
return $this->current();
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenate tokens from the current one to one of the specified and returns the string.
|
||||
@ -356,12 +355,12 @@ class Tokenizer {
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($token1 /*, $token2 ...*/) {
|
||||
if($this->curr && $this->_valid(func_get_args(), $this->curr[0])) {
|
||||
return $this->curr[1];
|
||||
} else {
|
||||
if($this->curr && $this->_valid(func_get_args(), $this->curr[0])) {
|
||||
return $this->curr[1];
|
||||
} else {
|
||||
throw new UnexpectedException($this, func_get_args());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step back
|
||||
@ -374,7 +373,7 @@ class Tokenizer {
|
||||
$this->p--;
|
||||
unset($this->prev, $this->curr, $this->next);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy load properties
|
||||
@ -395,31 +394,31 @@ class Tokenizer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key of the current element
|
||||
* @link http://php.net/manual/en/iterator.key.php
|
||||
* @return mixed scalar on success, or null on failure.
|
||||
*/
|
||||
public function key() {
|
||||
return $this->curr ? $this->curr[0] : null;
|
||||
}
|
||||
/**
|
||||
* Return the key of the current element
|
||||
* @link http://php.net/manual/en/iterator.key.php
|
||||
* @return mixed scalar on success, or null on failure.
|
||||
*/
|
||||
public function key() {
|
||||
return $this->curr ? $this->curr[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current position is valid
|
||||
* @link http://php.net/manual/en/iterator.valid.php
|
||||
* @return boolean The return value will be casted to boolean and then evaluated.
|
||||
* Returns true on success or false on failure.
|
||||
*/
|
||||
public function valid() {
|
||||
return (bool)$this->curr;
|
||||
}
|
||||
/**
|
||||
* Checks if current position is valid
|
||||
* @link http://php.net/manual/en/iterator.valid.php
|
||||
* @return boolean The return value will be casted to boolean and then evaluated.
|
||||
* Returns true on success or false on failure.
|
||||
*/
|
||||
public function valid() {
|
||||
return (bool)$this->curr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind the Iterator to the first element. Disabled.
|
||||
* @link http://php.net/manual/en/iterator.rewind.php
|
||||
* @return void Any returned value is ignored.
|
||||
*/
|
||||
public function rewind() {}
|
||||
/**
|
||||
* Rewind the Iterator to the first element. Disabled.
|
||||
* @link http://php.net/manual/en/iterator.rewind.php
|
||||
* @return void Any returned value is ignored.
|
||||
*/
|
||||
public function rewind() {}
|
||||
|
||||
/**
|
||||
* Get token name
|
||||
@ -428,16 +427,16 @@ class Tokenizer {
|
||||
* @return string
|
||||
*/
|
||||
public static function getName($token) {
|
||||
if(is_string($token)) {
|
||||
return $token;
|
||||
} elseif(is_integer($token)) {
|
||||
return token_name($token);
|
||||
} elseif(is_array($token)) {
|
||||
if(is_string($token)) {
|
||||
return $token;
|
||||
} elseif(is_integer($token)) {
|
||||
return token_name($token);
|
||||
} elseif(is_array($token)) {
|
||||
return token_name($token[0]);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whitespace of current token
|
||||
@ -499,15 +498,15 @@ class Tokenizer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements of an object
|
||||
* @link http://php.net/manual/en/countable.count.php
|
||||
* @return int The custom count as an integer.
|
||||
* The return value is cast to an integer.
|
||||
*/
|
||||
public function count() {
|
||||
return $this->_max;
|
||||
}
|
||||
/**
|
||||
* Count elements of an object
|
||||
* @link http://php.net/manual/en/countable.count.php
|
||||
* @return int The custom count as an integer.
|
||||
* The return value is cast to an integer.
|
||||
*/
|
||||
public function count() {
|
||||
return $this->_max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tokens near current token
|
||||
@ -516,20 +515,20 @@ class Tokenizer {
|
||||
* @return array
|
||||
*/
|
||||
public function getSnippet($before = 0, $after = 0) {
|
||||
$from = 0;
|
||||
$to = $this->p;
|
||||
if($before > 0) {
|
||||
if($before > $this->p) {
|
||||
$from = $this->p;
|
||||
} else {
|
||||
$from = $before;
|
||||
}
|
||||
} elseif($before < 0) {
|
||||
$from = $this->p + $before;
|
||||
if($from < 0) {
|
||||
$from = 0;
|
||||
}
|
||||
}
|
||||
$from = 0;
|
||||
$to = $this->p;
|
||||
if($before > 0) {
|
||||
if($before > $this->p) {
|
||||
$from = $this->p;
|
||||
} else {
|
||||
$from = $before;
|
||||
}
|
||||
} elseif($before < 0) {
|
||||
$from = $this->p + $before;
|
||||
if($from < 0) {
|
||||
$from = 0;
|
||||
}
|
||||
}
|
||||
if($after > 0) {
|
||||
$to = $this->p + $after;
|
||||
if($to > $this->_max) {
|
||||
@ -543,13 +542,13 @@ class Tokenizer {
|
||||
} elseif($this->p > $this->_max) {
|
||||
$to = $this->_max;
|
||||
}
|
||||
$code = array();
|
||||
for($i=$from; $i<=$to; $i++) {
|
||||
$code[] = $this->tokens[ $i ];
|
||||
}
|
||||
$code = array();
|
||||
for($i=$from; $i<=$to; $i++) {
|
||||
$code[] = $this->tokens[ $i ];
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return snippet as string
|
||||
@ -596,19 +595,6 @@ class Tokenizer {
|
||||
return $this->curr ? $this->curr[3] : $this->_last_no;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump (append) token into variable
|
||||
*
|
||||
* @param mixed $var
|
||||
* @param bool $whitespace include whitespace
|
||||
*/
|
||||
/*public function appendTo(&$var, $whitespace = false) {
|
||||
$var .= $this->curr[1];
|
||||
if($whitespace && $this->curr[2]) {
|
||||
$var .= $this->curr[2];
|
||||
}
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Parse code and append tokens. This method move pointer to offset.
|
||||
* @param string $code
|
||||
@ -642,20 +628,20 @@ class TokenizeException extends \RuntimeException {}
|
||||
* Unexpected token
|
||||
*/
|
||||
class UnexpectedException extends TokenizeException {
|
||||
public function __construct(Tokenizer $tokens, $expect = null) {
|
||||
public function __construct(Tokenizer $tokens, $expect = null, $where = null) {
|
||||
if($expect && count($expect) == 1 && is_string($expect[0])) {
|
||||
$expect = ", expect '".$expect[0]."'";
|
||||
} else {
|
||||
$expect = "";
|
||||
}
|
||||
if(!$tokens->curr) {
|
||||
$this->message = "Unexpected end of expression$expect";
|
||||
$this->message = "Unexpected end of ".($where?:"expression")."$expect";
|
||||
} elseif($tokens->curr[1] === "\n") {
|
||||
$this->message = "Unexpected new line$expect";
|
||||
} elseif($tokens->curr[0] === T_WHITESPACE) {
|
||||
$this->message = "Unexpected whitespace$expect";
|
||||
} else {
|
||||
$this->message = "Unexpected token '".$tokens->current()."'$expect";
|
||||
$this->message = "Unexpected token '".$tokens->current()."' in ".($where?:"expression")."$expect";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user