';
+});
+$blade->addMethod('runtime','row',static function() {
+ $parent=BladeOne::$instance->lastControlStack()['args']; // getting the values of the parent control using the stack
+ $result='';
+ foreach($parent['values'] as $v) {
+ $result.=BladeOne::$instance->runChild('auto.test2_control',[$parent['alias']=>$v]);
+ }
+ return $result;
+});
+$blade->addMethod('runtime','row2',function() {
+ $parent=BladeOne::$instance->lastControlStack()['args']; // getting the values of the parent control using the stack
+ $result='';
+ foreach($parent['values'] as $v) {
+ $result.="
+@foreach($items as $item)
+ @card($item)
+@endforeach
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/BladeOne.php b/lib/BladeOne.php
index b96ec05..13bf55c 100644
--- a/lib/BladeOne.php
+++ b/lib/BladeOne.php
@@ -1,5 +1,4 @@
- public const VERSION = '4.10';
+ public const VERSION = '4.11';
/** @var int BladeOne reads if the compiled file has changed. If it has changed,then the file is replaced. */
public const MODE_AUTO = 0;
/** @var int Then compiled file is always replaced. It's slow and it's useful for development. */
@@ -75,10 +73,24 @@ class BladeOne
public $csrf_token = '';
/** @var string The path to the missing translations log file. If empty then every missing key is not saved. */
public $missingLog = '';
- /** @var bool */
+ /** @var bool if true then pipes commands are available, example {{$a1|strtolower}} */
public $pipeEnable = false;
/** @var array Alias (with or without namespace) of the classes */
public $aliasClasses = [];
+ protected $hierarcy = [];
+ /**
+ * @var callable[] associative array with the callable methods. The key must be the name of the method
+ * example:
+ * ```php
+ * $this->methods['compileAlert']=static function(?string $expression=null) { return };
+ * $this->methods['runtimeAlert']=function(?array $arguments=null) { return };
+ * ```
+ */
+ protected $methods = [];
+ protected $controlStack=[['name'=>'','args'=>[],'parent'=>0]];
+ protected $controlStackParent=0;
+ /** @var BladeOne it is used to get the last instance */
+ public static $instance;
/**
* @var bool if true then the variables defined in the "include" as arguments are scoped to work only
* inside the "include" statement.
@@ -97,13 +109,13 @@ class BladeOne
public $includeScope = false;
/**
* @var callable[] It allows to parse the compiled output using a function.
- * This function doesn't require to return a value
- * **Example:** this converts all compiled result in uppercase (note, content is a ref)
- * ```php
- * $this->compileCallbacks[]= static function (&$content, $templatename=null) {
+ * This function doesn't require to return a value
+ * **Example:** this converts all compiled result in uppercase (note, content is a ref)
+ * ```php
+ * $this->compileCallbacks[]= static function (&$content, $templatename=null) {
* $content=strtoupper($content);
- * };
- * ```
+ * };
+ * ```
*/
public $compileCallbacks = [];
/** @var array All the registered extensions. */
@@ -153,9 +165,7 @@ class BladeOne
* **sha1** the filename is converted into a sha1 hash
* **md5** the filename is converted into a md5 hash
*/
- protected $compileTypeFileName='auto';
-
-
+ protected $compileTypeFileName = 'auto';
/** @var array Custom "directive" dictionary. Those directives run at compile time. */
protected $customDirectives = [];
/** @var bool[] Custom directive dictionary. Those directives run at runtime. */
@@ -223,11 +233,8 @@ class BladeOne
protected $switchCount = 0;
/** @var bool Indicates if the switch is recently open */
protected $firstCaseInSwitch = true;
-
//
-
//
-
/**
* Bob the constructor.
* The folder at $compiledPath is created in case it doesn't exist.
@@ -247,15 +254,15 @@ public function __construct($templatePath = null, $compiledPath = null, $mode =
$this->templatePath = (is_array($templatePath)) ? $templatePath : [$templatePath];
$this->compiledPath = $compiledPath;
$this->setMode($mode);
- $this->authCallBack = function (
+ self::$instance = $this;
+ $this->authCallBack = function(
$action = null,
/** @noinspection PhpUnusedParameterInspection */
$subject = null
) {
return \in_array($action, $this->currentPermission, true);
};
-
- $this->authAnyCallBack = function ($array = []) {
+ $this->authAnyCallBack = function($array = []) {
foreach ($array as $permission) {
if (\in_array($permission, $this->currentPermission ?? [], true)) {
return true;
@@ -263,22 +270,19 @@ public function __construct($templatePath = null, $compiledPath = null, $mode =
}
return false;
};
-
- $this->errorCallBack = static function (
+ $this->errorCallBack = static function(
/** @noinspection PhpUnusedParameterInspection */
$key = null
) {
return false;
};
-
-
// If the "traits" has "Constructors", then we call them.
// Requisites.
// 1- the method must be public or protected
// 2- it must don't have arguments
// 3- It must have the name of the trait. i.e. trait=MyTrait, method=MyTrait()
$traits = get_declared_traits();
- $currentTraits = (array) class_uses($this);
+ $currentTraits = (array)class_uses($this);
foreach ($traits as $trait) {
$r = explode('\\', $trait);
$name = end($r);
@@ -290,9 +294,121 @@ public function __construct($templatePath = null, $compiledPath = null, $mode =
}
}
}
+
+ /**
+ * It gets an instance of Bladeone. If none, then it will create a new one witht eh default data.
+ * @param string|array $templatePath If null then it uses (caller_folder)/views
+ * @param string $compiledPath If null then it uses (caller_folder)/compiles
+ * @param int $mode
+ * =[BladeOne::MODE_AUTO,BladeOne::MODE_DEBUG,BladeOne::MODE_FAST,BladeOne::MODE_SLOW][$i]
+ * @return BladeOne
+ */
+ public static function getInstance($templatePath = null, $compiledPath = null, $mode = 0): BladeOne
+ {
+ if (self::$instance === null) {
+ new self($templatePath, $compiledPath, $mode);
+ }
+ return self::$instance;
+ }
+
+ /**
+ * It adds a control to the stack
+ * **Example:**
+ * ```php
+ * $this->addControlStackChild('alert',['message'=>'hello']);
+ * ```
+ * @param string $name the nametag of the stack
+ * @param array $args
+ * @return void
+ */
+ public function addControlStackChild(string $name,array $args): void
+ {
+ $this->controlStack[]=['name'=>$name,'args'=>$args,'parent'=>$this->controlStackParent];
+ $this->controlStackParent=array_key_last($this->controlStack);
+ }
+ public function addControlStackSibling(string $name,array $args): void
+ {
+ $grandparent=$this->controlStack[$this->controlStackParent]['parent'];
+ $this->controlStack[]=['name'=>$name,'args'=>$args,'parent'=>$grandparent];
+ }
+
+ /**
+ * It returns the lastest control from the stack and removes it.
+ * @return mixed|null
+ */
+ public function closeControlStack() {
+ $this->controlStackParent=$this->controlStack[$this->controlStackParent]['parent'];
+ return array_pop($this->controlStack);
+ }
+ /**
+ * It removes the last parent and returns the new parent (the previous grandparent)
+ * Usually this method and closeControlStack must return the same if every child was closed correctly.
+ * @return mixed|null
+ */
+ public function closeControlStackParent() {
+ $grandparent=$this->controlStack[$this->controlStackParent]['parent'];
+ unset($this->controlStack[$this->controlStackParent]);
+ $this->controlStackParent=$grandparent;
+ return $this->controlStack[$this->controlStackParent];
+ }
+ /**
+ * It returns the last control from the stack without removing it.
+ * It is useful to get the previous control, it could be a parent or a sibling.
+ * @return array
+ */
+ public function lastControlStack(): array
+ {
+ return @end($this->controlStack);
+ }
+
+ /**
+ * It gets the parent control stack
+ * @return array
+ */
+ public function parentControlStack(): array
+ {
+ return $this->controlStack[$this->controlStackParent];
+ }
+
+ /**
+ * It clears the whole control stack
+ * @return void
+ */
+ public function clearControlStack(): void
+ {
+ $this->controlStack=[['name'=>'','args'=>[],'parent'=>0]];
+ }
+
+ /**
+ * It adds a new method
+ * **Example:**
+ * ```php
+ * $this->addMethod('compile','alert',static function(?string $expression=null) { return });
+ * $this->addMethod('runtime','alert',function(?array $arguments=null) { return });
+ * ```
+ * @param string $type=['compile','runtime'][$i] if you want to add a compile method or a runtime method
+ * @param string $name the name of the method. Commonly it is in lowercase.
+ * @param callable $callable the callable method
+ * @return BladeOne
+ */
+ public function addMethod(string $type,string $name,callable $callable): BladeOne
+ {
+ $fullName=$type.ucfirst($name);
+ $this->methods[$fullName]=$callable;
+ return $this;
+ }
+
+ /**
+ * It clears all the methods defined.
+ * @return $this
+ */
+ public function clearMethods(): self
+ {
+ $this->methods=[];
+ return $this;
+ }
//
//
-
/**
* Show an error in the web.
*
@@ -309,7 +425,6 @@ public function showError($id, $text, $critic = false, $alwaysThrow = false): st
if ($this->throwOnError || $alwaysThrow || $critic === true) {
throw new \RuntimeException("BladeOne Error [$id] $text");
}
-
$msg = "
\n";
@@ -336,7 +451,7 @@ public static function e($value): string
return \htmlentities(\print_r($value, true), ENT_QUOTES, 'UTF-8', false);
}
if (\is_numeric($value)) {
- $value=(string)$value;
+ $value = (string)$value;
}
return \htmlentities($value, ENT_QUOTES, 'UTF-8', false);
}
@@ -373,27 +488,25 @@ public function format($variable, $format = null): string
* ```
*
* @param ?string $input The input value
- * @param string $quote The quote used (to quote the result)
- * @param bool $parse If the result will be parsed or not. If false then it's returned without $this->e
+ * @param string $quote The quote used (to quote the result)
+ * @param bool $parse If the result will be parsed or not. If false then it's returned without $this->e
* @return string
*/
public function wrapPHP($input, $quote = '"', $parse = true): string
{
- if($input===null) {
+ if ($input === null) {
return 'null';
}
if (strpos($input, '(') !== false && !$this->isQuoted($input)) {
if ($parse) {
return $quote . $this->phpTagEcho . '$this->e(' . $input . ');?>' . $quote;
}
-
return $quote . $this->phpTagEcho . $input . ';?>' . $quote;
}
if (strpos($input, '$') === false) {
if ($parse) {
return self::enq($input);
}
-
return $input;
}
if ($parse) {
@@ -430,7 +543,7 @@ public static function enq($value): string
if (\is_array($value) || \is_object($value)) {
return \htmlentities(\print_r($value, true), ENT_NOQUOTES, 'UTF-8', false);
}
- return \htmlentities($value??'', ENT_NOQUOTES, 'UTF-8', false);
+ return \htmlentities($value ?? '', ENT_NOQUOTES, 'UTF-8', false);
}
/**
@@ -443,7 +556,7 @@ public function addInclude($view, $alias = null): void
$alias = \explode('.', $view);
$alias = \end($alias);
}
- $this->directive($alias, function ($expression) use ($view) {
+ $this->directive($alias, function($expression) use ($view) {
$expression = $this->stripParentheses($expression) ?: '[]';
return "$this->phpTag echo \$this->runChild('$view', $expression); ?>";
});
@@ -473,11 +586,9 @@ public function stripParentheses($expression): string
if (\is_null($expression)) {
return '';
}
-
if (static::startsWith($expression, '(')) {
$expression = \substr($expression, 1, -1);
}
-
return $expression;
}
@@ -501,7 +612,6 @@ public static function startsWith($haystack, $needles): bool
}
}
}
-
return false;
}
@@ -568,7 +678,6 @@ public function addAliasClasses($aliasName, $classWithNS): void
}
//
//
-
/**
* Authentication. Sets with a user,role and permission
*
@@ -587,20 +696,17 @@ public function setAuth($user = '', $role = null, $permission = []): void
* run the blade engine. It returns the result of the code.
*
* @param string $string HTML to parse
- * @param array $data It is an associative array with the datas to display.
+ * @param array $data It is an associative array with the datas to display.
* @return string It returns a parsed string
* @throws Exception
*/
public function runString($string, $data = []): string
{
$php = $this->compileString($string);
-
$obLevel = \ob_get_level();
\ob_start();
\extract($data, EXTR_SKIP);
-
$previousError = \error_get_last();
-
try {
@eval('?' . '>' . $php);
} catch (Exception $e) {
@@ -615,7 +721,6 @@ public function runString($string, $data = []): string
$this->showError('runString', $e->getMessage() . ' ' . $e->getCode(), true);
return '';
}
-
$lastError = \error_get_last(); // PHP 5.6
if ($previousError != $lastError && $lastError['type'] == E_PARSE) {
while (\ob_get_level() > $obLevel) {
@@ -624,7 +729,6 @@ public function runString($string, $data = []): string
$this->showError('runString', $lastError['message'] . ' ' . $lastError['type'], true);
return '';
}
-
return \ob_get_clean();
}
@@ -668,7 +772,7 @@ public function compileString($value): string
*/
protected function storeVerbatimBlocks($value): string
{
- return \preg_replace_callback('/(?verbatimBlocks[] = $matches[1];
return $this->verbatimPlaceholder;
}, $value);
@@ -705,7 +809,7 @@ protected function parseToken($token): string
*/
protected function restoreVerbatimBlocks($result): string
{
- $result = \preg_replace_callback('/' . \preg_quote($this->verbatimPlaceholder) . '/', function () {
+ $result = \preg_replace_callback('/' . \preg_quote($this->verbatimPlaceholder) . '/', function() {
return \array_shift($this->verbatimBlocks);
}, $result);
$this->verbatimBlocks = [];
@@ -803,7 +907,6 @@ public function startPush($section, $content = ''): void
/*
* endswitch tag
*/
-
/**
* Append content to a given push section.
*
@@ -899,16 +1002,16 @@ protected function extendStartPush($section, $content): void
*/
public function yieldPushContent($section, $default = ''): string
{
- if($section===null || $section==='') {
+ if ($section === null || $section === '') {
return $default;
}
- if($section[-1]==='*') {
- $keys=array_keys($this->pushes);
- $findme=rtrim($section,'*');
- $result="";
- foreach($keys as $key) {
- if(strpos($key,$findme)===0) {
- $result.=\implode(\array_reverse($this->pushes[$key]));
+ if ($section[-1] === '*') {
+ $keys = array_keys($this->pushes);
+ $findme = rtrim($section, '*');
+ $result = "";
+ foreach ($keys as $key) {
+ if (strpos($key, $findme) === 0) {
+ $result .= \implode(\array_reverse($this->pushes[$key]));
}
}
return $result;
@@ -944,7 +1047,6 @@ public function splitForeach($each = 1, $splitText = ',', $splitEnd = ''): strin
} else {
$eachN = PHP_INT_MAX;
}
-
if (($loopStack['index'] + 1) % $eachN === 0) {
return $splitText;
}
@@ -1024,22 +1126,19 @@ public function __call($name, $args)
public function registerIfStatement($name, callable $callback): string
{
$this->conditions[$name] = $callback;
-
- $this->directive($name, function ($expression) use ($name) {
+ $this->directive($name, function($expression) use ($name) {
$tmp = $this->stripParentheses($expression);
return $expression !== ''
? $this->phpTag . " if (\$this->check('$name', $tmp)): ?>"
: $this->phpTag . " if (\$this->check('$name')): ?>";
});
-
- $this->directive('else' . $name, function ($expression) use ($name) {
+ $this->directive('else' . $name, function($expression) use ($name) {
$tmp = $this->stripParentheses($expression);
return $expression !== ''
? $this->phpTag . " elseif (\$this->check('$name', $tmp)): ?>"
: $this->phpTag . " elseif (\$this->check('$name')): ?>";
});
-
- $this->directive('end' . $name, function () {
+ $this->directive('end' . $name, function() {
return $this->phpTag . ' endif; ?>';
});
return '';
@@ -1073,14 +1172,15 @@ public function includeWhen($bool = false, $view = '', $value = []): string
}
/**
- * Macro of function run
+ * Macro of function run. Runchild backups the operations, so it is ideal to run as a child process without
+ * intervining with other processes.
*
* @param $view
* @param array $variables
* @return string
* @throws Exception
*/
- public function runChild($view, $variables = []): string
+ public function runChild($view,$variables = []): string
{
if (\is_array($variables)) {
if ($this->includeScope) {
@@ -1089,13 +1189,10 @@ public function runChild($view, $variables = []): string
$backup = null;
}
$newVariables = \array_merge($this->variables, $variables);
+ $backupControlStack=$this->controlStack;
+ $backupSectionStack=$this->sectionStack;
+ $backupLookStack=$this->loopsStack;
} else {
- if ($variables === null) {
- $newVariables = $this->variables;
- var_dump($newVariables);
- die(1);
- }
-
$this->showError('run/include', "RunChild: Include/run variables should be defined as array ['idx'=>'value']", true);
return '';
}
@@ -1103,6 +1200,9 @@ public function runChild($view, $variables = []): string
if ($backup !== null) {
$this->variables = $backup;
}
+ $this->controlStack=$backupControlStack;
+ $this->sectionStack=$backupSectionStack;
+ $this->loopsStack=$backupLookStack;
return $r;
}
@@ -1285,22 +1385,18 @@ public function compile($templateName = null, $forced = false)
public function getCompiledFile($templateName = ''): string
{
$templateName = (empty($templateName)) ? $this->fileName : $templateName;
-
$fullPath = $this->getTemplateFile($templateName);
- if($fullPath == '') {
- throw new \RuntimeException('Template not found: ' . $templateName);
+ if ($fullPath == '') {
+ throw new \RuntimeException('Template not found: ' .($this->mode == self::MODE_DEBUG ? $this->templatePath[0].'/'.$templateName : $templateName));
}
-
- $style=$this->compileTypeFileName;
- if ($style==='auto') {
- $style='sha1';
+ $style = $this->compileTypeFileName;
+ if ($style === 'auto') {
+ $style = 'sha1';
}
$hash = $style === 'md5' ? \md5($fullPath) : \sha1($fullPath);
return $this->compiledPath . '/' . basename($templateName) . '_' . $hash . $this->compileExtension;
}
-
-
/**
* Get the mode of the engine.See BladeOne::MODE_* constants
*
@@ -1344,7 +1440,6 @@ public function getTemplateFile($templateName = ''): string
// it's in the root of the template folder.
return $this->locateTemplate($templateName . $this->fileExtension);
}
-
$file = $arr[$c - 1];
\array_splice($arr, $c - 1, $c - 1); // delete the last element
$path = \implode('/', $arr);
@@ -1365,7 +1460,6 @@ protected function locateTemplate($name): string
if (\is_file($path)) {
return $path;
}
-
$this->notFoundPath .= $path . ",";
}
return '';
@@ -1594,7 +1688,6 @@ public function csrfIsValid($alwaysRegenerate = false, $tokenId = '_token'): boo
$this->csrf_token = $_POST[$tokenId] ?? null; // ping pong the token.
return $this->csrf_token . '|' . $this->ipClient() === ($_SESSION[$tokenId] ?? null);
}
-
if ($this->csrf_token == '' || $alwaysRegenerate) {
// if not token then we generate a new one
$this->regenerateToken($tokenId);
@@ -1753,7 +1846,6 @@ public function yieldContent($section, $default = ''): string
if (isset($this->sections[$section])) {
return \str_replace($this->PARENTKEY, $default, $this->sections[$section]);
}
-
return $default;
}
@@ -1890,6 +1982,7 @@ public function setCompiledExtension($fileExtension): void
{
$this->compileExtension = $fileExtension;
}
+
/**
* @return string
* @see BladeOne::setCompileTypeFileName
@@ -1904,7 +1997,7 @@ public function getCompileTypeFileName(): string
* * **auto** (default mode) the mode is "sha1"
* * **sha1** the filename is converted into a sha1 hash (it's the slow method, but it is safest)
* * **md5** the filename is converted into a md5 hash (it's faster than sha1, and it uses less space)
- * @param string $compileTypeFileName=['auto','sha1','md5'][$i]
+ * @param string $compileTypeFileName =['auto','sha1','md5'][$i]
* @return BladeOne
*/
public function setCompileTypeFileName(string $compileTypeFileName): BladeOne
@@ -1912,6 +2005,7 @@ public function setCompileTypeFileName(string $compileTypeFileName): BladeOne
$this->compileTypeFileName = $compileTypeFileName;
return $this;
}
+
/**
* Add new loop to the stack.
*
@@ -1945,7 +2039,6 @@ public function incrementLoopIndices(): object
{
$c = \count($this->loopsStack) - 1;
$loop = &$this->loopsStack[$c];
-
$loop['index']++;
$loop['iteration']++;
$loop['first'] = $loop['index'] == 0;
@@ -1991,7 +2084,6 @@ public function getFirstLoop(): ?object
public function renderEach($view, $data, $iterator, $empty = 'raw|'): string
{
$result = '';
-
if (\count($data) > 0) {
// If is actually data in the array, we will loop through the data and append
// an instance of the partial view to the final result HTML passing in the
@@ -2019,7 +2111,6 @@ public function renderEach($view, $data, $iterator, $empty = 'raw|'): string
public function run($view = null, $variables = []): string
{
$mode = $this->getMode();
-
if ($view === null) {
$view = $this->viewStack;
}
@@ -2028,9 +2119,8 @@ public function run($view = null, $variables = []): string
$this->showError('run', 'BladeOne: view not set', true);
return '';
}
-
- $forced = ($mode & 1)!==0; // mode=1 forced:it recompiles no matter if the compiled file exists or not.
- $runFast = ($mode & 2)!==0; // mode=2 runfast: the code is not compiled neither checked, and it runs directly the compiled
+ $forced = ($mode & 1) !== 0; // mode=1 forced:it recompiles no matter if the compiled file exists or not.
+ $runFast = ($mode & 2) !== 0; // mode=2 runfast: the code is not compiled neither checked, and it runs directly the compiled
$this->sections = [];
if ($mode == 3) {
$this->showError('run', "we can't force and run fast at the same time", true);
@@ -2067,7 +2157,8 @@ public function setView($view): BladeOne
* $this->composer(); // clear all composer.
* ```
*
- * @param string|array|null $view It could contain wildcards (*). Example: 'aa.bb.cc','*.bb.cc','aa.bb.*','*.bb.*'
+ * @param string|array|null $view It could contain wildcards (*). Example:
+ * 'aa.bb.cc','*.bb.cc','aa.bb.*','*.bb.*'
*
* @param callable|string|null $functionOrClass
* @return BladeOne
@@ -2085,7 +2176,6 @@ public function composer($view = null, $functionOrClass = null): BladeOne
} else {
$this->composerStack[$view] = $functionOrClass;
}
-
return $this;
}
@@ -2100,9 +2190,7 @@ public function startComponent($name, array $data = []): void
{
if (\ob_start()) {
$this->componentStack[] = $name;
-
$this->componentData[$this->currentComponent()] = $data;
-
$this->slots[$this->currentComponent()] = [];
}
}
@@ -2171,7 +2259,6 @@ public function slot($name, $content = null): void
$this->slots[$this->currentComponent()][$name] = $content;
} elseif (\ob_start()) {
$this->slots[$this->currentComponent()][$name] = '';
-
$this->slotStack[$this->currentComponent()][] = $name;
}
}
@@ -2184,11 +2271,9 @@ public function slot($name, $content = null): void
public function endSlot(): void
{
static::last($this->componentStack);
-
$currentSlot = \array_pop(
$this->slotStack[$this->currentComponent()]
);
-
$this->slots[$this->currentComponent()][$currentSlot] = \trim(\ob_get_clean());
}
@@ -2456,7 +2541,6 @@ public function setErrorFunction(callable $fn): void
//
//
-
/**
* Get the entire loop stack.
*
@@ -2532,7 +2616,6 @@ public function _e($phrase): string
$this->missingTranslation($phrase);
return $phrase;
}
-
return static::$dictionary[$phrase];
}
@@ -2574,7 +2657,6 @@ public function _n($phrase, $phrases, $num = 0): string
$this->missingTranslation($phrase);
return ($num <= 1) ? $phrase : $phrases;
}
-
return ($num <= 1) ? $this->_e($phrase) : $this->_e($phrases);
}
@@ -2613,7 +2695,6 @@ protected function compileSwitch($expression): string
}
//
//
-
protected function compileDump($expression): string
{
return $this->phpTagEcho . "\$this->dump$expression;?>";
@@ -2627,7 +2708,6 @@ protected function compileRelative($expression): string
protected function compileMethod($expression): string
{
$v = $this->stripParentheses($expression);
-
return "";
}
@@ -2783,7 +2863,7 @@ protected function getEchoMethods(): array
'compileEscapedEchos' => \strlen(\stripcslashes($this->escapedTags[0])),
'compileRegularEchos' => \strlen(\stripcslashes($this->contentTags[0])),
];
- \uksort($methods, static function ($method1, $method2) use ($methods) {
+ \uksort($methods, static function($method1, $method2) use ($methods) {
// Ensure the longest tags are processed first
if ($methods[$method1] > $methods[$method2]) {
return -1;
@@ -2828,7 +2908,7 @@ protected function compileStatements($value)
*
* @return mixed|string
*/
- $callback = function ($match) {
+ $callback = function($match) {
if (static::contains($match[1], '@')) {
// @@escaped tag
$match[0] = isset($match[3]) ? $match[1] . $match[3] : $match[1];
@@ -2846,10 +2926,23 @@ protected function compileStatements($value)
$this->stripParentheses(static::get($match, 3))
);
}
- } elseif (\method_exists($this, $method = 'compile' . \ucfirst($match[1]))) {
- // it calls the function compile
- $match[0] = $this->$method(static::get($match, 3));
} else {
+ $nameMethod = 'compile' . \ucfirst($match[1]);
+ if (isset($this->methods[$nameMethod])) {
+ return $this->methods[$nameMethod](static::get($match, 3));
+ }
+ if (\method_exists($this, $nameMethod)) {
+ // it calls the function compile
+ return $this->$nameMethod(static::get($match, 3));
+ }
+ $nameMethod = 'runtime' . \ucfirst($match[1]);
+ $m4 = $match[4]??'';
+ if (isset($this->methods[$nameMethod])) {
+ return $this->autoruntime($m4, $nameMethod);
+ }
+ if (\method_exists($this, $nameMethod)) {
+ return $this->autoruntime($m4, $nameMethod, true);
+ }
return $match[0];
}
}
@@ -2859,6 +2952,31 @@ protected function compileStatements($value)
return preg_replace_callback('/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', $callback, $value);
}
+ /**
+ * This function generates a php code to run a runtime method.
+ * @param string|null $expression the expression to add in the code.
+ * For compile, it is of the type "($a2,"222")"
+ * For runtime, it is of the time "arg1=$a2 arg2="222""
+ * @param string $nameFunction The name of the function.
+ * @param bool $compileMethod If the method is a compiled method, or it is a runtime method.
+ * @return string
+ */
+ protected function autoruntime(?string $expression, string $nameFunction, $compileMethod = false): string
+ {
+
+ if ($compileMethod) {
+ return $this->wrapPHP("\$this->$nameFunction$expression", '', false);
+ }
+ $args = $this->parseArgs($expression, ' ','=',false);
+
+ $argsV = '[';
+ foreach ($args as $k => $v) {
+ $argsV .= "'$k'=>$v,";
+ }
+ $argsV .= ']';
+ return $this->wrapPHP("\$this->methods['$nameFunction']($argsV)", '', false);
+ }
+
/**
* Determine if a given string contains a given substring.
*
@@ -2879,7 +2997,6 @@ public static function contains($haystack, $needles): bool
}
}
}
-
return false;
}
@@ -2888,7 +3005,6 @@ protected function compileStatementClass($match): string
if (isset($match[3])) {
return $this->phpTagEcho . $this->fixNamespaceClass($match[1]) . $match[3] . '; ?>';
}
-
return $this->phpTagEcho . $this->fixNamespaceClass($match[1]) . '(); ?>';
}
@@ -3052,6 +3168,7 @@ public function parseArgs($text, $separator = ',', $assigment = '=', $emptyKey =
}
}
*/
+
foreach ($parts as $part) {
$part = trim($part);
if ($part) {
@@ -3123,7 +3240,7 @@ public function parseArgsOld($text, $separator = ','): array
protected function compileRawEchos($value): string
{
$pattern = \sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->rawTags[0], $this->rawTags[1]);
- $callback = function ($matches) {
+ $callback = function($matches) {
$whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3];
return $matches[1] ? \substr(
$matches[0],
@@ -3145,7 +3262,6 @@ protected function compileEchoDefaults($value): string
{
// Source: https://www.php.net/manual/en/language.variables.basics.php
$patternPHPVariableName = '\$[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*';
-
$result = \preg_replace('/^(' . $patternPHPVariableName . ')\s+or\s+(.+?)$/s', 'isset($1) ? $1 : $2', $value);
if (!$this->pipeEnable) {
return $this->fixNamespaceClass($result);
@@ -3178,7 +3294,7 @@ protected function pipeDream($result): string
return $result;
}
$prev = '';
- for ($i = 1; $i <=$c; $i++) {
+ for ($i = 1; $i <= $c; $i++) {
$r = @explode(':', $array[$i], 2);
$fnName = trim($r[0]);
$fnNameF = $fnName[0]; // first character
@@ -3189,7 +3305,7 @@ protected function pipeDream($result): string
} elseif (method_exists($this, $fnName)) {
$fnName = '$this->' . $fnName;
}
- $hasArgument=count($r) === 2;
+ $hasArgument = count($r) === 2;
if ($i === 1) {
$prev = $fnName . '(' . $array[0];
if ($hasArgument) {
@@ -3199,13 +3315,12 @@ protected function pipeDream($result): string
} else {
$prev = $fnName . '(' . $prev;
if ($hasArgument) {
- $prev .=','. $r[1] . ')';
+ $prev .= ',' . $r[1] . ')';
} else {
- $prev.=')';
+ $prev .= ')';
}
}
}
-
return $prev;
}
@@ -3218,7 +3333,7 @@ protected function pipeDream($result): string
protected function compileRegularEchos($value): string
{
$pattern = \sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->contentTags[0], $this->contentTags[1]);
- $callback = function ($matches) {
+ $callback = function($matches) {
$whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3];
$wrapped = \sprintf($this->echoFormat, $this->compileEchoDefaults($matches[2]));
return $matches[1] ? \substr($matches[0], 1) : $this->phpTagEcho . $wrapped . '; ?>' . $whitespace;
@@ -3235,9 +3350,8 @@ protected function compileRegularEchos($value): string
protected function compileEscapedEchos($value): string
{
$pattern = \sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->escapedTags[0], $this->escapedTags[1]);
- $callback = function ($matches) {
+ $callback = function($matches) {
$whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3];
-
return $matches[1] ? $matches[0] : $this->phpTag
. \sprintf($this->echoFormat, $this->compileEchoDefaults($matches[2])) . '; ?>'
. $whitespace;
@@ -3320,7 +3434,6 @@ protected function compileAuth($expression = ''): string
if ($role == '') {
return $this->phpTag . 'if(isset($this->currentUser)): ?>';
}
-
return $this->phpTag . "if(isset(\$this->currentUser) && \$this->currentRole==$role): ?>";
}
@@ -3336,7 +3449,6 @@ protected function compileElseAuth($expression = ''): string
if ($role == '') {
return $this->phpTag . 'else: ?>';
}
-
return $this->phpTag . "elseif(isset(\$this->currentUser) && \$this->currentRole==$role): ?>";
}
@@ -3368,12 +3480,10 @@ protected function compileElseCan($expression = ''): string
if ($v) {
return $this->phpTag . 'elseif (call_user_func($this->authCallBack,' . $v . ')): ?>';
}
-
return $this->phpTag . 'else: ?>';
}
//
//
-
protected function compileCannot($expression): string
{
$v = $this->stripParentheses($expression);
@@ -3392,7 +3502,6 @@ protected function compileElseCannot($expression = ''): string
if ($v) {
return $this->phpTag . 'elseif (!call_user_func($this->authCallBack,' . $v . ')): ?>';
}
-
return $this->phpTag . 'else: ?>';
}
@@ -3435,12 +3544,10 @@ protected function compileGuest($expression = null): string
if ($expression === null) {
return $this->phpTag . 'if(!isset($this->currentUser)): ?>';
}
-
$role = $this->stripParentheses($expression);
if ($role == '') {
return $this->phpTag . 'if(!isset($this->currentUser)): ?>';
}
-
return $this->phpTag . "if(!isset(\$this->currentUser) || \$this->currentRole!=$role): ?>";
}
@@ -3456,7 +3563,6 @@ protected function compileElseGuest($expression): string
if ($role == '') {
return $this->phpTag . 'else: ?>';
}
-
return $this->phpTag . "elseif(!isset(\$this->currentUser) || \$this->currentRole!=$role): ?>";
}
@@ -3533,7 +3639,6 @@ protected function compileEndunless(): string
}
//
//
-
/**
* @error('key')
*
@@ -3578,7 +3683,6 @@ protected function compileFor($expression): string
}
//
//
-
/**
* Compile the foreach statements into valid PHP.
*
@@ -3588,7 +3692,7 @@ protected function compileFor($expression): string
protected function compileForeach($expression): string
{
//\preg_match('/\( *(.*) * as *([^\)]*)/', $expression, $matches);
- if($expression===null) {
+ if ($expression === null) {
return '@foreach';
}
\preg_match('/\( *(.*) * as *([^)]*)/', $expression, $matches);
@@ -3656,7 +3760,6 @@ protected function compileIf($expression): string
}
//
//
-
/**
* Compile the else-if statements into valid PHP.
*
@@ -3826,7 +3929,6 @@ protected function compileExtends($expression): string
return $this->phpTag . '$_shouldextend[' . $this->uidCounter . ']=1; ?>';
}
-
/**
* Execute the @parent command. This operation works in tandem with extendSection
*
@@ -4041,9 +4143,7 @@ protected function compileJSon($expression): string
return $this->phpTagEcho . "json_encode($parts[0], $options, $depth); ?>";
}
//
-
//
-
protected function compileIsset($expression): string
{
return $this->phpTag . "if(isset$expression): ?>";
@@ -4073,7 +4173,6 @@ protected function injectClass($className, $variableName = null)
if (isset($this->injectResolver)) {
return call_user_func($this->injectResolver, $className, $variableName);
}
-
$fullClassName = $className . "\\" . $variableName;
return new $fullClassName();
}
@@ -4117,8 +4216,6 @@ protected function compile_n($expression): string
}
//
-
-
//
public static function isCli(): bool
{
@@ -4176,14 +4273,14 @@ public static function colorLog($str, $type = 'i'): string
public function checkHealthPath(): bool
{
echo self::colorLog("Checking Health\n");
- $status=true;
+ $status = true;
if (is_dir($this->compiledPath)) {
echo "Compile-path [$this->compiledPath] is a folder " . self::colorLog("OK") . "\n";
} else {
- $status=false;
+ $status = false;
echo "Compile-path [$this->compiledPath] is not a folder " . self::colorLog("ERROR", 'e') . "\n";
}
- foreach($this->templatePath as $t) {
+ foreach ($this->templatePath as $t) {
if (is_dir($t)) {
echo "Template-path (view) [$t] is a folder " . self::colorLog("OK") . "\n";
} else {
@@ -4197,12 +4294,12 @@ public function checkHealthPath(): bool
$rnd = $this->compiledPath . '/dummy' . rand(10000, 900009);
$f = @file_put_contents($rnd, 'dummy');
if ($f === false) {
- $status=false;
+ $status = false;
$error = self::colorLog("Unable to create file [" . $this->compiledPath . '/dummy]', 'e');
}
@unlink($rnd);
} catch (\Throwable $ex) {
- $status=false;
+ $status = false;
$error = self::colorLog($ex->getMessage(), 'e');
}
echo "Testing write in the compile folder [$rnd] $error\n";
@@ -4211,10 +4308,11 @@ public function checkHealthPath(): bool
echo "View(s) found :" . count($files) . "\n";
return $status;
}
+
public function createFolders(): void
{
echo self::colorLog("Creating Folder\n");
- echo "Creating compile folder[".self::colorLog($this->compiledPath,'b')."] ";
+ echo "Creating compile folder[" . self::colorLog($this->compiledPath, 'b') . "] ";
if (!\is_dir($this->compiledPath)) {
$ok = @\mkdir($this->compiledPath, 0770, true);
if ($ok === false) {
@@ -4225,8 +4323,8 @@ public function createFolders(): void
} else {
echo self::colorLog("Note: folder already exist.\n", 'w');
}
- foreach($this->templatePath as $t) {
- echo "Creating template folder [".self::colorLog($t,'b')."] ";
+ foreach ($this->templatePath as $t) {
+ echo "Creating template folder [" . self::colorLog($t, 'b') . "] ";
if (!\is_dir($t)) {
$ok = @\mkdir($t, 0770, true);
if ($ok === false) {
@@ -4282,24 +4380,24 @@ public function cliEngine(): void
$done = true;
$this->clearcompile();
}
- if($createfolder) {
- $done=true;
+ if ($createfolder) {
+ $done = true;
$this->createFolders();
}
if (!$done) {
echo " Syntax:\n";
- echo " ".self::colorLog("-templatepath","b")." (optional) the template-path (view path).\n";
+ echo " " . self::colorLog("-templatepath", "b") . " (optional) the template-path (view path).\n";
echo " Default value: 'views'\n";
echo " Example: 'php /vendor/bin/bladeonecli /folder/views' (absolute)\n";
echo " Example: 'php /vendor/bin/bladeonecli folder/view1' (relative)\n";
- echo " ".self::colorLog("-compilepath","b")." (optional) the compile-path.\n";
+ echo " " . self::colorLog("-compilepath", "b") . " (optional) the compile-path.\n";
echo " Default value: 'compiles'\n";
echo " Example: 'php /vendor/bin/bladeonecli /folder/compiles' (absolute)\n";
echo " Example: 'php /vendor/bin/bladeonecli compiles' (relative)\n";
- echo " ".self::colorLog("-createfolder","b")." it creates the folders if they don't exist.\n";
+ echo " " . self::colorLog("-createfolder", "b") . " it creates the folders if they don't exist.\n";
echo " Example: php ./vendor/bin/bladeonecli -createfolder\n";
- echo " ".self::colorLog("-clearcompile","b")." It deletes the content of the compile path\n";
- echo " ".self::colorLog("-check","b")." It checks the folders and permissions\n";
+ echo " " . self::colorLog("-clearcompile", "b") . " It deletes the content of the compile path\n";
+ echo " " . self::colorLog("-check", "b") . " It checks the folders and permissions\n";
}
}
@@ -4314,6 +4412,14 @@ public static function isAbsolutePath($path): bool
}
return $path[1] === ':';
}
-
//
}
+if (! function_exists("array_key_last")) {
+ function array_key_last($array) {
+ if (!is_array($array) || empty($array)) {
+ return NULL;
+ }
+
+ return array_keys($array)[count($array)-1];
+ }
+}
diff --git a/tests/AbstractBladeTestCase.php b/tests/AbstractBladeTestCase.php
index 83e2317..fb26331 100644
--- a/tests/AbstractBladeTestCase.php
+++ b/tests/AbstractBladeTestCase.php
@@ -1,37 +1,38 @@
-
- * @since 16/09/2018
- */
-abstract class AbstractBladeTestCase extends TestCase {
- const TEMPLATE_PATH = __DIR__ . '/resources/templates';
- const COMPILED_PATH = __DIR__ . '/resources/compiled';
-
- protected $blade;
- public function __construct($name = null, array $data = [], $dataName = '') {
- parent::__construct($name, $data, $dataName);
- $this->blade = new BladeOne(self::TEMPLATE_PATH, self::COMPILED_PATH, BladeOne::MODE_SLOW);
- }
- /*
- // tearDown on php7.2 is implemented as tearDown():void. However PHP 5.6 doesn't allows it.
- // So I comment this line because it breaks Travis.
- protected function tearDown() {
- // Remove files compiled in this test to prevent side effects.
- array_map('unlink', glob(self::COMPILED_PATH . "/*"));
- }
- */
-
- public function assertEqualsIgnoringWhitespace($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) {
- $this->assertEquals(
- preg_replace('/\s/', '', $expected),
- preg_replace('/\s/', '', $actual),
- $message, $delta, $maxDepth, $canonicalize, $ignoreCase
- );
- }
-}
\ No newline at end of file
+
+ * @since 16/09/2018
+ */
+abstract class AbstractBladeTestCase extends TestCase {
+ const TEMPLATE_PATH = __DIR__ . '/resources/templates';
+ const COMPILED_PATH = __DIR__ . '/resources/compiled';
+
+ protected $blade;
+ public function __construct($name = null, array $data = [], $dataName = '') {
+ parent::__construct($name, $data, $dataName);
+ $this->blade = new BladeOne(self::TEMPLATE_PATH, self::COMPILED_PATH, BladeOne::MODE_DEBUG);
+
+ }
+ /*
+ // tearDown on php7.2 is implemented as tearDown():void. However PHP 5.6 doesn't allows it.
+ // So I comment this line because it breaks Travis.
+ protected function tearDown() {
+ // Remove files compiled in this test to prevent side effects.
+ array_map('unlink', glob(self::COMPILED_PATH . "/*"));
+ }
+ */
+
+ public function assertEqualsIgnoringWhitespace($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) {
+ $this->assertEquals(
+ preg_replace('/\s/', '', $expected),
+ preg_replace('/\s/', '', $actual),
+ $message, $delta, $maxDepth, $canonicalize, $ignoreCase
+ );
+ }
+}
diff --git a/tests/autoTest.php b/tests/autoTest.php
new file mode 100644
index 0000000..7eb3180
--- /dev/null
+++ b/tests/autoTest.php
@@ -0,0 +1,81 @@
+clearMethods();
+ BladeOne::$instance->addMethod('runtime', 'one', static function($args) {
+ return "method one " . $args['a1'] . ',' . $args['a2'];
+ });
+ BladeOne::$instance->addMethod('compile', 'two', function($args) {
+ return BladeOne::$instance->wrapPHP("eftec\\tests\\sum$args", false, false);
+ });
+ $this->assertEquals("it is test 1\nmethod one hola,mundo\ntwo:6",
+ BladeOne::$instance->run("auto.test1", ['a1' => 10, 'a2' => 20]));
+ }
+
+ public function test2()
+ {
+ // it clears the previous methods created in different tests
+ BladeOne::$instance->clearMethods();
+ BladeOne::$instance->addMethod('runtime', 'table', function($args) {
+ // you could use array merge to set a default value, or use conditions, ternary operators, etc.
+ $args = array_merge(['alias' => 'alias'], $args);
+ // we store the current control in the stack, and we turn @table as the current parent
+ BladeOne::$instance->addControlStackChild('table', $args);
+ return '
';
+ });
+ BladeOne::$instance->addMethod('runtime', 'endtable', function($args) {
+ // it gets the last control, parent or child
+ $latest=BladeOne::$instance->lastControlStack();
+ // optionally you can add a validator and validate if the previous tag is the correnct
+ if($latest['name']!=='table') {
+ // it shows an error and throw an exception
+ BladeOne::$instance->showError('@endtable', 'Missing @table',true,true);
+ }
+ BladeOne::$instance->closeControlStackParent(); // it closes the parent (@table)
+ return '
';
+ });
+ BladeOne::$instance->addMethod('runtime', 'row', function() {
+ // getting the values of the parent control (@table) using the stack
+ // note: we don't need to add a child everytime a new control is added, its optional
+ $parent = BladeOne::$instance->parentControlStack()['args'];
+ $result = '';
+ foreach ($parent['values'] as $v) {
+ $result .= BladeOne::$instance->runChild('auto.test2_control', [$parent['alias'] => $v]);
+ }
+ return $result;
+ });
+ BladeOne::$instance->addMethod('runtime', 'row2', function() {
+ // getting the values of the parent control (@table) using the stack
+ // note: we don't need to add a child everytime a new control is added, its optional
+ $parent = BladeOne::$instance->parentControlStack()['args'];
+ $result = '';
+ foreach ($parent['values'] as $v) {
+ $result .= "