Done #5, dev #66, improve blocks, refectory block's tests

This commit is contained in:
Ivan Shalganov
2014-02-14 15:55:36 +04:00
parent dc287f08b2
commit 52c0858d06
16 changed files with 231 additions and 106 deletions

View File

@@ -99,6 +99,11 @@ class Fenom
*/
protected $_compile_dir = "/tmp";
/**
* @var string[] compile directory for custom provider
*/
protected $_compiles = array();
/**
* @var int masked options
*/
@@ -213,8 +218,7 @@ class Fenom
'type' => self::BLOCK_COMPILER,
'open' => 'Fenom\Compiler::tagBlockOpen',
'close' => 'Fenom\Compiler::tagBlockClose',
'tags' => array(// 'parent' => 'Fenom\Compiler::tagParent' // not implemented yet
),
'tags' => array('parent' => 'Fenom\Compiler::tagParent'),
'float_tags' => array('parent' => 1)
),
'extends' => array( // {extends ...}
@@ -355,7 +359,7 @@ class Fenom
}
/**
* @param callable $cbcd
* @param callable $cb
* @return self
*/
public function addTagFilter($cb)
@@ -620,11 +624,15 @@ class Fenom
*
* @param string $scm scheme name
* @param Fenom\ProviderInterface $provider provider object
* @param string $compile_path
* @return $this
*/
public function addProvider($scm, \Fenom\ProviderInterface $provider)
public function addProvider($scm, \Fenom\ProviderInterface $provider, $compile_path = null)
{
$this->_providers[$scm] = $provider;
if($compile_path) {
$this->_compiles[$scm] = $compile_path;
}
return $this;
}
@@ -730,7 +738,11 @@ class Fenom
public function getTemplate($template, $options = 0)
{
$options |= $this->_options;
$key = dechex($options) . "@" . $template;
if(is_array($template)) {
$key = dechex($options) . "@" . implode(",", $template);
} else {
$key = dechex($options) . "@" . $template;
}
if (isset($this->_storage[$key])) {
/** @var Fenom\Template $tpl */
$tpl = $this->_storage[$key];
@@ -766,13 +778,13 @@ class Fenom
/**
* Load template from cache or create cache if it doesn't exists.
*
* @param string $tpl
* @param string $template
* @param int $opts
* @return Fenom\Render
*/
protected function _load($tpl, $opts)
protected function _load($template, $opts)
{
$file_name = $this->_getCacheName($tpl, $opts);
$file_name = $this->_getCacheName($template, $opts);
if (is_file($this->_compile_dir . "/" . $file_name)) {
$fenom = $this; // used in template
$_tpl = include($this->_compile_dir . "/" . $file_name);
@@ -781,7 +793,7 @@ class Fenom
return $_tpl;
}
}
return $this->compile($tpl, true, $opts);
return $this->compile($template, true, $opts);
}
/**
@@ -793,14 +805,22 @@ class Fenom
*/
private function _getCacheName($tpl, $options)
{
$hash = $tpl . ":" . $options;
return sprintf("%s.%x.%x.php", str_replace(":", "_", basename($tpl)), crc32($hash), strlen($hash));
if(is_array($tpl)) {
$hash = implode(".", $tpl) . ":" . $options;
foreach($tpl as &$t) {
$t = str_replace(":", "_", basename($t));
}
return implode("~", $tpl).".".sprintf("%x.%x.php", crc32($hash), strlen($hash));
} else {
$hash = $tpl . ":" . $options;
return sprintf("%s.%x.%x.php", str_replace(":", "_", basename($tpl)), crc32($hash), strlen($hash));
}
}
/**
* Compile and save template
*
* @param string $tpl
* @param string|array $tpl
* @param bool $store store template on disk
* @param int $options
* @throws RuntimeException
@@ -809,7 +829,15 @@ class Fenom
public function compile($tpl, $store = true, $options = 0)
{
$options = $this->_options | $options;
$template = $this->getRawTemplate()->load($tpl);
if(is_string($tpl)) {
$template = $this->getRawTemplate()->load($tpl);
} else {
$template = $this->getRawTemplate()->load($tpl[0], false);
unset($tpl[0]);
foreach($tpl as $t) {
$template->extend($t);
}
}
if ($store) {
$cache = $this->_getCacheName($tpl, $options);
$tpl_tmp = tempnam($this->_compile_dir, $cache);

View File

@@ -549,33 +549,13 @@ class Compiler
public static function tagUse(Tokenizer $tokens, Template $tpl)
{
if ($tpl->getStackSize()) {
throw new InvalidUsageException("Tags {use} can not be nested");
throw new InvalidUsageException("Tag {use} can not be nested");
}
$cname = $tpl->parsePlainArg($tokens, $name);
$tpl->parsePlainArg($tokens, $name);
if ($name) {
$donor = $tpl->getStorage()->getRawTemplate()->load($name, false);
$donor->_extended = true;
$donor->_extends = $tpl;
$donor->_compatible = & $tpl->_compatible;
//$donor->blocks = &$tpl->blocks;
$donor->compile();
$blocks = $donor->blocks;
foreach ($blocks as $name => $code) {
if (isset($tpl->blocks[$name])) {
$tpl->blocks[$name] = $code;
unset($blocks[$name]);
}
}
$tpl->uses = $blocks + $tpl->uses;
$tpl->addDepend($donor);
return '?>' . $donor->getBody() . '<?php ';
$tpl->importBlocks($name);
} else {
throw new InvalidUsageException('template name must be given explicitly yet');
// under construction
// $tpl->_compatible = true;
// return '$donor = $tpl->getStorage()->getTemplate(' . $cname . ', \Fenom\Template::EXTENDED);' . PHP_EOL .
// '$donor->fetch((array)$tpl);' . PHP_EOL .
// '$tpl->b += (array)$donor->b';
}
}
@@ -583,8 +563,8 @@ class Compiler
* Tag {block ...}
* @param Tokenizer $tokens
* @param Scope $scope
* @throws \RuntimeException
* @return string
* @throws InvalidUsageException
*/
public static function tagBlockOpen(Tokenizer $tokens, Scope $scope)
{
@@ -592,77 +572,58 @@ class Compiler
$scope->tpl->_compatible = true;
}
$scope["cname"] = $scope->tpl->parsePlainArg($tokens, $name);
if(!$name) {
throw new \RuntimeException("Only static names for blocks allowed");
}
$scope["name"] = $name;
$scope["use_parent"] = false;
}
/**
* Close tag {/block}
* @param Tokenizer $tokens
* @param Scope $scope
*/
public static function tagBlockClose($tokens, Scope $scope)
{
$tpl = $scope->tpl;
$name = $scope["name"];
if(isset($tpl->blocks[$name])) { // block defined
$block = &$tpl->blocks[$name];
if($block['use_parent']) {
$parent = $scope->getContent();
$block['block'] = str_replace($block['use_parent']." ?>", "?>".$parent, $block['block']);
}
if(!$block["import"]) { // not from {use} - redefine block
$scope->replaceContent($block["block"]);
return;
} elseif($block["import"] != $tpl->getName()) { // tag {use} was in another template
$tpl->blocks[$scope["name"]]["import"] = false;
$scope->replaceContent($block["block"]);
}
}
$tpl->blocks[$scope["name"]] = [
"from" => $tpl->getName(),
"import" => false,
"use_parent" => $scope["use_parent"],
"block" => $scope->getContent()
];
}
/**
* Tag {parent}
*
* @param Tokenizer $tokens
* @param Scope $scope
* @return string
*/
public static function tagBlockClose($tokens, Scope $scope)
{
$tpl = $scope->tpl;
if (isset($tpl->_extends)) { // is child
if ($scope["name"]) { // is scalar name
if ($tpl->_compatible) { // is compatible mode
$scope->replaceContent(
'<?php /* 1) Block ' . $tpl . ': ' . $scope["cname"] . ' */' . PHP_EOL . ' if(empty($tpl->b[' . $scope["cname"] . '])) { ' .
'$tpl->b[' . $scope["cname"] . '] = function($tpl) { ?>' . PHP_EOL .
$scope->getContent() .
"<?php };" .
"} ?>" . PHP_EOL
);
} elseif (!isset($tpl->blocks[$scope["name"]])) { // is block not registered
$tpl->blocks[$scope["name"]] = $scope->getContent();
$scope->replaceContent(
'<?php /* 2) Block ' . $tpl . ': ' . $scope["cname"] . ' ' . $tpl->_compatible . ' */' . PHP_EOL . ' $tpl->b[' . $scope["cname"] . '] = function($tpl) { ?>' . PHP_EOL .
$scope->getContent() .
"<?php }; ?>" . PHP_EOL
);
}
} else { // dynamic name
$tpl->_compatible = true; // enable compatible mode
$scope->replaceContent(
'<?php /* 3) Block ' . $tpl . ': ' . $scope["cname"] . ' */' . PHP_EOL . ' if(empty($tpl->b[' . $scope["cname"] . '])) { ' .
'$tpl->b[' . $scope["cname"] . '] = function($tpl) { ?>' . PHP_EOL .
$scope->getContent() .
"<?php };" .
"} ?>" . PHP_EOL
);
}
} else { // is parent
if (isset($tpl->blocks[$scope["name"]])) { // has block
if ($tpl->_compatible) { // compatible mode enabled
$scope->replaceContent(
'<?php /* 4) Block ' . $tpl . ': ' . $scope["cname"] . ' */' . PHP_EOL . ' if(isset($tpl->b[' . $scope["cname"] . '])) { echo $tpl->b[' . $scope["cname"] . ']->__invoke($tpl); } else {?>' . PHP_EOL .
$tpl->blocks[$scope["name"]] .
'<?php } ?>' . PHP_EOL
);
} else {
$scope->replaceContent($tpl->blocks[$scope["name"]]);
}
// } elseif(isset($tpl->_extended) || !empty($tpl->_compatible)) {
} elseif (isset($tpl->_extended) && $tpl->_compatible || empty($tpl->_extended)) {
$scope->replaceContent(
'<?php /* 5) Block ' . $tpl . ': ' . $scope["cname"] . ' */' . PHP_EOL . ' if(isset($tpl->b[' . $scope["cname"] . '])) { echo $tpl->b[' . $scope["cname"] . ']->__invoke($tpl); } else {?>' . PHP_EOL .
$scope->getContent() .
'<?php } ?>' . PHP_EOL
);
}
}
return '';
}
public static function tagParent($tokens, Scope $scope)
{
if (empty($scope->tpl->_extends)) {
throw new InvalidUsageException("Tag {parent} may be declared in children");
$block_scope = $scope->tpl->getParentScope('block');
if(!$block_scope['use_parent']) {
$block_scope['use_parent'] = "/* %%parent#".mt_rand(0, 1e6)."%% */";
}
return $block_scope['use_parent'];
}
/**
@@ -1035,4 +996,4 @@ class Compiler
$scope->tpl->escape = $scope["escape"];
}
}
}

View File

@@ -155,6 +155,21 @@ class Template extends Render
return count($this->_stack);
}
/**
* @param string $tag
* @return bool|\Fenom\Scope
*/
public function getParentScope($tag)
{
for($i = count($this->_stack) - 1; $i>=0; $i--) {
if($this->_stack[$i]->name == $tag) {
return $this->_stack[$i];
}
}
return false;
}
/**
* Load source from provider
* @param string $name
@@ -443,6 +458,7 @@ class Template extends Render
// evaluate template's code
eval("\$this->_code = " . $this->_getClosureSource() . ";\n\$this->_macros = " . $this->_getMacrosArray() . ';');
if (!$this->_code) {
var_dump($this->getBody());exit;
throw new CompileException("Fatal error while creating the template");
}
}
@@ -474,6 +490,36 @@ class Template extends Render
}
}
/**
* Import block from another template
* @param string $tpl
*/
public function importBlocks($tpl) {
$donor = $this->_fenom->compile($tpl, false);
foreach($donor->blocks as $name => $block) {
if(!isset($this->blocks[ $name ])) {
$block['import'] = $this->getName();
$this->blocks[ $name ] = $block;
}
}
$this->addDepend($donor);
}
/**
* Extends the template
* @param string $tpl
*/
public function extend($tpl) {
$this->compile();
$parent = $this->_fenom->getRawTemplate()->load($tpl, false);
$parent->blocks = &$this->blocks;
$parent->_options = $this->_options;
$parent->compile();
$this->_body = $parent->_body;
$this->_src = $parent->_src;
$this->addDepend($parent);
}
/**
* Tag router
* @param Tokenizer $tokens