From bfbbe54c9b32b5780b2338e82ad392a702456730 Mon Sep 17 00:00:00 2001 From: Anne Zijlstra Date: Tue, 9 Sep 2025 16:30:39 +0200 Subject: [PATCH 01/10] Add support for shorttags in functions --- src/Compile/FunctionCallCompiler.php | 55 +++++++++++++--------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/src/Compile/FunctionCallCompiler.php b/src/Compile/FunctionCallCompiler.php index 107dd98bb..04f78dcf1 100644 --- a/src/Compile/FunctionCallCompiler.php +++ b/src/Compile/FunctionCallCompiler.php @@ -3,39 +3,17 @@ * Smarty Internal Plugin Compile Registered Function * Compiles code for the execution of a registered function * - - * @author Uwe Tews */ namespace Smarty\Compile; use Smarty\Compiler\Template; -use Smarty\CompilerException; /** * Smarty Internal Plugin Compile Registered Function Class - * - - */ class FunctionCallCompiler extends Base { - - /** - * Attribute definition: Overwrites base class. - * - * @var array - * @see BasePlugin - */ - public $optional_attributes = ['_any']; - - /** - * Shorttag attribute order defined by its names - * - * @var array - */ - protected $shorttag_order = []; - /** * Compiles code for the execution of a registered function * @@ -51,14 +29,8 @@ class FunctionCallCompiler extends Base { */ public function compile($args, Template $compiler, $parameter = [], $tag = null, $function = null): string { - - // check and get attributes - $_attr = $this->getAttributes($compiler, $args); - unset($_attr['nocache']); - - $_paramsArray = $this->formatParamsArray($_attr); - $_params = 'array(' . implode(',', $_paramsArray) . ')'; - + // Compile arguments to pass on the the functionhandler + $_params = $this->compileArguments($args); if ($functionHandler = $compiler->getSmarty()->getFunctionHandler($function)) { @@ -76,4 +48,27 @@ public function compile($args, Template $compiler, $parameter = [], $tag = null, return $output; } + + /** + * Recursively compile function arguments. + * @param array $args array with attributes from parser + * @return string compiled arguments + */ + private function compileArguments(array $arguments): string + { + $params = ''; + + foreach ($arguments as $key => $value) { + $params .= var_export($key, true) . "=>"; + if (is_array($value)) { + $params .= $this->compileArguments($value); + } else { + $params .= $value; + } + + $params .= ','; + } + + return '[' . rtrim($params, ',') . ']'; + } } From 36e6fc5b86e3d102016ffd0895888ab18e0beab5 Mon Sep 17 00:00:00 2001 From: Anne Zijlstra Date: Wed, 10 Sep 2025 11:42:40 +0200 Subject: [PATCH 02/10] Change implementation --- src/Compile/FunctionCallCompiler.php | 77 ++++++++++++------- src/FunctionHandler/AttributeBase.php | 77 +++++++++++++++++++ .../AttributeFunctionHandlerInterface.php | 15 ++++ 3 files changed, 141 insertions(+), 28 deletions(-) create mode 100644 src/FunctionHandler/AttributeBase.php create mode 100644 src/FunctionHandler/AttributeFunctionHandlerInterface.php diff --git a/src/Compile/FunctionCallCompiler.php b/src/Compile/FunctionCallCompiler.php index 04f78dcf1..a5540ff32 100644 --- a/src/Compile/FunctionCallCompiler.php +++ b/src/Compile/FunctionCallCompiler.php @@ -9,11 +9,44 @@ namespace Smarty\Compile; use Smarty\Compiler\Template; +use Smarty\CompilerException; +use Smarty\FunctionHandler\AttributeFunctionHandlerInterface; /** * Smarty Internal Plugin Compile Registered Function Class + * */ -class FunctionCallCompiler extends Base { +class FunctionCallCompiler extends Base +{ + /** + * Array of names of required attribute required by tag + * + * @var array + */ + protected $required_attributes = []; + + /** + * Attribute definition: Overwrites base class. + * + * @var array + * @see BasePlugin + */ + public $optional_attributes = ['_any']; + + /** + * Shorttag attribute order defined by its names + * + * @var array + */ + protected $shorttag_order = []; + + /** + * Array of names of valid option flags + * + * @var array + */ + protected $option_flags = []; + /** * Compiles code for the execution of a registered function * @@ -29,10 +62,21 @@ class FunctionCallCompiler extends Base { */ public function compile($args, Template $compiler, $parameter = [], $tag = null, $function = null): string { - // Compile arguments to pass on the the functionhandler - $_params = $this->compileArguments($args); - if ($functionHandler = $compiler->getSmarty()->getFunctionHandler($function)) { + // add attributes of the function handler. + if ($functionHandler instanceof AttributeFunctionHandlerInterface) { + $supported_attributes = $functionHandler->getSupportedAttributes(); + + foreach (['required_attributes', 'optional_attributes', 'shorttag_order', 'option_flags'] as $property) { + $this->$property = $supported_attributes[$property]; + } + } + + // check and get attributes + $_attr = $this->getAttributes($compiler, $args); + + $_paramsArray = $this->formatParamsArray($_attr); + $_params = 'array(' . implode(',', $_paramsArray) . ')'; // not cacheable? $compiler->tag_nocache = $compiler->tag_nocache || !$functionHandler->isCacheable(); @@ -48,27 +92,4 @@ public function compile($args, Template $compiler, $parameter = [], $tag = null, return $output; } - - /** - * Recursively compile function arguments. - * @param array $args array with attributes from parser - * @return string compiled arguments - */ - private function compileArguments(array $arguments): string - { - $params = ''; - - foreach ($arguments as $key => $value) { - $params .= var_export($key, true) . "=>"; - if (is_array($value)) { - $params .= $this->compileArguments($value); - } else { - $params .= $value; - } - - $params .= ','; - } - - return '[' . rtrim($params, ',') . ']'; - } -} +} \ No newline at end of file diff --git a/src/FunctionHandler/AttributeBase.php b/src/FunctionHandler/AttributeBase.php new file mode 100644 index 000000000..71a035dc9 --- /dev/null +++ b/src/FunctionHandler/AttributeBase.php @@ -0,0 +1,77 @@ +cacheable; + } + + /** + * Function body + * @param mixed $params The supplied parameters. + * @param Smarty\Template $template + * @return mixed + */ + abstract public function handle($params, Template $template): ?string; + + /** + * Return the support attributes for this function. + * @return array + */ + public function getSupportedAttributes(): array + { + return [ + 'required_attributes' => $this->required_attributes, + 'optional_attributes' => $this->optional_attributes, + 'shorttag_order' => $this->shorttag_order, + 'option_flags' => $this->option_flags, + ]; + } +} diff --git a/src/FunctionHandler/AttributeFunctionHandlerInterface.php b/src/FunctionHandler/AttributeFunctionHandlerInterface.php new file mode 100644 index 000000000..7b9ca1753 --- /dev/null +++ b/src/FunctionHandler/AttributeFunctionHandlerInterface.php @@ -0,0 +1,15 @@ + + */ + public function getSupportedAttributes(): array; +} \ No newline at end of file From bef42dea137c083b38a371ed55f5f76ac57e8262 Mon Sep 17 00:00:00 2001 From: Anne Zijlstra Date: Wed, 10 Sep 2025 12:03:48 +0200 Subject: [PATCH 03/10] Revert removal of nocache default optionflag as it breaks unittests --- src/Compile/FunctionCallCompiler.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Compile/FunctionCallCompiler.php b/src/Compile/FunctionCallCompiler.php index a5540ff32..60f400d7a 100644 --- a/src/Compile/FunctionCallCompiler.php +++ b/src/Compile/FunctionCallCompiler.php @@ -45,7 +45,7 @@ class FunctionCallCompiler extends Base * * @var array */ - protected $option_flags = []; + protected $option_flags = ['nocache']; /** * Compiles code for the execution of a registered function @@ -74,6 +74,7 @@ public function compile($args, Template $compiler, $parameter = [], $tag = null, // check and get attributes $_attr = $this->getAttributes($compiler, $args); + unset($_attr['nocache']); $_paramsArray = $this->formatParamsArray($_attr); $_params = 'array(' . implode(',', $_paramsArray) . ')'; From 4cfee0da114f07f90b77fdfd9da6ccd23dc362e8 Mon Sep 17 00:00:00 2001 From: Anne Zijlstra Date: Wed, 10 Sep 2025 15:07:35 +0200 Subject: [PATCH 04/10] Add small tweaks --- src/Compile/FunctionCallCompiler.php | 6 ++++-- src/FunctionHandler/AttributeFunctionHandlerInterface.php | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Compile/FunctionCallCompiler.php b/src/Compile/FunctionCallCompiler.php index 60f400d7a..25a0f4927 100644 --- a/src/Compile/FunctionCallCompiler.php +++ b/src/Compile/FunctionCallCompiler.php @@ -68,7 +68,9 @@ public function compile($args, Template $compiler, $parameter = [], $tag = null, $supported_attributes = $functionHandler->getSupportedAttributes(); foreach (['required_attributes', 'optional_attributes', 'shorttag_order', 'option_flags'] as $property) { - $this->$property = $supported_attributes[$property]; + if (isset($supported_attributes[$property]) && is_array($supported_attributes[$property])) { + $this->$property = $supported_attributes[$property]; + } } } @@ -93,4 +95,4 @@ public function compile($args, Template $compiler, $parameter = [], $tag = null, return $output; } -} \ No newline at end of file +} diff --git a/src/FunctionHandler/AttributeFunctionHandlerInterface.php b/src/FunctionHandler/AttributeFunctionHandlerInterface.php index 7b9ca1753..f76259ecd 100644 --- a/src/FunctionHandler/AttributeFunctionHandlerInterface.php +++ b/src/FunctionHandler/AttributeFunctionHandlerInterface.php @@ -12,4 +12,4 @@ interface AttributeFunctionHandlerInterface extends FunctionHandlerInterface * @return array */ public function getSupportedAttributes(): array; -} \ No newline at end of file +} From be4a4d0912678b734c51db093c7abaf0d01c3494 Mon Sep 17 00:00:00 2001 From: Simon Wisselink Date: Mon, 22 Sep 2025 13:03:04 +0200 Subject: [PATCH 05/10] refactored getAttributes into a special Compiler class --- src/Compile/AttributesCompiler.php | 143 +++++++++++++++++++++++++++ src/Compile/Base.php | 84 ++-------------- src/Compile/FunctionCallCompiler.php | 41 +++----- 3 files changed, 164 insertions(+), 104 deletions(-) create mode 100644 src/Compile/AttributesCompiler.php diff --git a/src/Compile/AttributesCompiler.php b/src/Compile/AttributesCompiler.php new file mode 100644 index 000000000..5578ff065 --- /dev/null +++ b/src/Compile/AttributesCompiler.php @@ -0,0 +1,143 @@ +required_attributes = $required_attributes; + $this->optional_attributes = $optional_attributes; + $this->shorttag_order = $shorttag_order; + $this->option_flags = $option_flags; + } + + /** + * This function checks if the attributes passed are valid + * The attributes passed for the tag to compile are checked against the list of required and + * optional attributes. Required attributes must be present. Optional attributes are check against + * the corresponding list. The keyword '_any' specifies that any attribute will be accepted + * as valid + * + * @param object $compiler compiler object + * @param array $attributes attributes applied to the tag + * + * @return array of mapped attributes for further processing + */ + public function getAttributes($compiler, $attributes) { + $_indexed_attr = []; + $options = array_fill_keys($this->option_flags, true); + foreach ($attributes as $key => $mixed) { + // shorthand ? + if (!is_array($mixed)) { + // options flag ? + if (isset($options[trim($mixed, '\'"')])) { + $_indexed_attr[trim($mixed, '\'"')] = true; + // shorthand attribute ? + } elseif (isset($this->shorttag_order[$key])) { + $_indexed_attr[$this->shorttag_order[$key]] = $mixed; + } else { + // too many shorthands + $compiler->trigger_template_error('too many shorthand attributes', null, true); + } + // named attribute + } else { + foreach ($mixed as $k => $v) { + // options flag? + if (isset($options[$k])) { + if (is_bool($v)) { + $_indexed_attr[$k] = $v; + } else { + if (is_string($v)) { + $v = trim($v, '\'" '); + } + + // Mapping array for boolean option value + static $optionMap = [1 => true, 0 => false, 'true' => true, 'false' => false]; + + if (isset($optionMap[$v])) { + $_indexed_attr[$k] = $optionMap[$v]; + } else { + $compiler->trigger_template_error( + "illegal value '" . var_export($v, true) . + "' for options flag '{$k}'", + null, + true + ); + } + } + // must be named attribute + } else { + $_indexed_attr[$k] = $v; + } + } + } + } + // check if all required attributes present + foreach ($this->required_attributes as $attr) { + if (!isset($_indexed_attr[$attr])) { + $compiler->trigger_template_error("missing '{$attr}' attribute", null, true); + } + } + // check for not allowed attributes + if ($this->optional_attributes !== ['_any']) { + $allowedAttributes = array_fill_keys( + array_merge( + $this->required_attributes, + $this->optional_attributes, + $this->option_flags + ), + true + ); + foreach ($_indexed_attr as $key => $dummy) { + if (!isset($allowedAttributes[$key]) && $key !== 0) { + $compiler->trigger_template_error("unexpected '{$key}' attribute", null, true); + } + } + } + // default 'false' for all options flags not set + foreach ($this->option_flags as $flag) { + if (!isset($_indexed_attr[$flag])) { + $_indexed_attr[$flag] = false; + } + } + + return $_indexed_attr; + } + +} \ No newline at end of file diff --git a/src/Compile/Base.php b/src/Compile/Base.php index 2d5c0c0ef..94d7d8d68 100644 --- a/src/Compile/Base.php +++ b/src/Compile/Base.php @@ -82,84 +82,12 @@ protected function formatParamsArray(array $_attr): array { * @return array of mapped attributes for further processing */ protected function getAttributes($compiler, $attributes) { - $_indexed_attr = []; - $options = array_fill_keys($this->option_flags, true); - foreach ($attributes as $key => $mixed) { - // shorthand ? - if (!is_array($mixed)) { - // options flag ? - if (isset($options[trim($mixed, '\'"')])) { - $_indexed_attr[trim($mixed, '\'"')] = true; - // shorthand attribute ? - } elseif (isset($this->shorttag_order[$key])) { - $_indexed_attr[$this->shorttag_order[$key]] = $mixed; - } else { - // too many shorthands - $compiler->trigger_template_error('too many shorthand attributes', null, true); - } - // named attribute - } else { - foreach ($mixed as $k => $v) { - // options flag? - if (isset($options[$k])) { - if (is_bool($v)) { - $_indexed_attr[$k] = $v; - } else { - if (is_string($v)) { - $v = trim($v, '\'" '); - } - - // Mapping array for boolean option value - static $optionMap = [1 => true, 0 => false, 'true' => true, 'false' => false]; - - if (isset($optionMap[$v])) { - $_indexed_attr[$k] = $optionMap[$v]; - } else { - $compiler->trigger_template_error( - "illegal value '" . var_export($v, true) . - "' for options flag '{$k}'", - null, - true - ); - } - } - // must be named attribute - } else { - $_indexed_attr[$k] = $v; - } - } - } - } - // check if all required attributes present - foreach ($this->required_attributes as $attr) { - if (!isset($_indexed_attr[$attr])) { - $compiler->trigger_template_error("missing '{$attr}' attribute", null, true); - } - } - // check for not allowed attributes - if ($this->optional_attributes !== ['_any']) { - $allowedAttributes = array_fill_keys( - array_merge( - $this->required_attributes, - $this->optional_attributes, - $this->option_flags - ), - true - ); - foreach ($_indexed_attr as $key => $dummy) { - if (!isset($allowedAttributes[$key]) && $key !== 0) { - $compiler->trigger_template_error("unexpected '{$key}' attribute", null, true); - } - } - } - // default 'false' for all options flags not set - foreach ($this->option_flags as $flag) { - if (!isset($_indexed_attr[$flag])) { - $_indexed_attr[$flag] = false; - } - } - - return $_indexed_attr; + return (new AttributesCompiler( + $this->required_attributes, + $this->optional_attributes, + $this->shorttag_order, + $this->option_flags + ))->getAttributes($compiler, $attributes); } /** diff --git a/src/Compile/FunctionCallCompiler.php b/src/Compile/FunctionCallCompiler.php index 25a0f4927..014bcccb3 100644 --- a/src/Compile/FunctionCallCompiler.php +++ b/src/Compile/FunctionCallCompiler.php @@ -9,21 +9,13 @@ namespace Smarty\Compile; use Smarty\Compiler\Template; -use Smarty\CompilerException; use Smarty\FunctionHandler\AttributeFunctionHandlerInterface; /** * Smarty Internal Plugin Compile Registered Function Class * */ -class FunctionCallCompiler extends Base -{ - /** - * Array of names of required attribute required by tag - * - * @var array - */ - protected $required_attributes = []; +class FunctionCallCompiler extends Base { /** * Attribute definition: Overwrites base class. @@ -40,13 +32,6 @@ class FunctionCallCompiler extends Base */ protected $shorttag_order = []; - /** - * Array of names of valid option flags - * - * @var array - */ - protected $option_flags = ['nocache']; - /** * Compiles code for the execution of a registered function * @@ -62,20 +47,23 @@ class FunctionCallCompiler extends Base */ public function compile($args, Template $compiler, $parameter = [], $tag = null, $function = null): string { + if ($functionHandler = $compiler->getSmarty()->getFunctionHandler($function)) { - // add attributes of the function handler. - if ($functionHandler instanceof AttributeFunctionHandlerInterface) { - $supported_attributes = $functionHandler->getSupportedAttributes(); - foreach (['required_attributes', 'optional_attributes', 'shorttag_order', 'option_flags'] as $property) { - if (isset($supported_attributes[$property]) && is_array($supported_attributes[$property])) { - $this->$property = $supported_attributes[$property]; - } - } + $attribute_overrides = []; + + if ($functionHandler instanceof AttributeFunctionHandlerInterface) { + $attribute_overrides = $functionHandler->getSupportedAttributes(); } - + // check and get attributes - $_attr = $this->getAttributes($compiler, $args); + $_attr = (new AttributesCompiler( + $attribute_overrides['required_attributes'] ?? $this->required_attributes, + $attribute_overrides['optional_attributes'] ?? $this->optional_attributes, + $attribute_overrides['shorttag_order'] ?? $this->shorttag_order, + $attribute_overrides['option_flags'] ?? $this->option_flags + ))->getAttributes($compiler, $args); + unset($_attr['nocache']); $_paramsArray = $this->formatParamsArray($_attr); @@ -95,4 +83,5 @@ public function compile($args, Template $compiler, $parameter = [], $tag = null, return $output; } + } From 362c870e4505779b02745fca1474c5177b884fbb Mon Sep 17 00:00:00 2001 From: Anne Zijlstra Date: Wed, 1 Oct 2025 12:12:18 +0200 Subject: [PATCH 06/10] Add unittests for attributecompiler class. --- ...utesCompiler.php => AttributeCompiler.php} | 15 +- src/Compile/Base.php | 2 +- src/Compile/FunctionCallCompiler.php | 12 +- .../Compile/AttributeCompilerTest.php | 189 ++++++++++++++++++ 4 files changed, 202 insertions(+), 16 deletions(-) rename src/Compile/{AttributesCompiler.php => AttributeCompiler.php} (96%) create mode 100644 tests/UnitTests/Compile/AttributeCompilerTest.php diff --git a/src/Compile/AttributesCompiler.php b/src/Compile/AttributeCompiler.php similarity index 96% rename from src/Compile/AttributesCompiler.php rename to src/Compile/AttributeCompiler.php index 5578ff065..077c4cfc3 100644 --- a/src/Compile/AttributesCompiler.php +++ b/src/Compile/AttributeCompiler.php @@ -2,9 +2,11 @@ namespace Smarty\Compile; -class AttributesCompiler +/** + * This class handles compiling the attributes. + */ +class AttributeCompiler { - /** * Array of names of required attributes required by tag * @@ -39,8 +41,7 @@ public function __construct( array $optional_attributes = [], array $shorttag_order = [], array $option_flags = [] - ) - { + ) { $this->required_attributes = $required_attributes; $this->optional_attributes = $optional_attributes; $this->shorttag_order = $shorttag_order; @@ -59,7 +60,8 @@ public function __construct( * * @return array of mapped attributes for further processing */ - public function getAttributes($compiler, $attributes) { + public function getAttributes($compiler, $attributes) + { $_indexed_attr = []; $options = array_fill_keys($this->option_flags, true); foreach ($attributes as $key => $mixed) { @@ -139,5 +141,4 @@ public function getAttributes($compiler, $attributes) { return $_indexed_attr; } - -} \ No newline at end of file +} diff --git a/src/Compile/Base.php b/src/Compile/Base.php index 94d7d8d68..101535012 100644 --- a/src/Compile/Base.php +++ b/src/Compile/Base.php @@ -82,7 +82,7 @@ protected function formatParamsArray(array $_attr): array { * @return array of mapped attributes for further processing */ protected function getAttributes($compiler, $attributes) { - return (new AttributesCompiler( + return (new AttributeCompiler( $this->required_attributes, $this->optional_attributes, $this->shorttag_order, diff --git a/src/Compile/FunctionCallCompiler.php b/src/Compile/FunctionCallCompiler.php index 014bcccb3..3ce50da2b 100644 --- a/src/Compile/FunctionCallCompiler.php +++ b/src/Compile/FunctionCallCompiler.php @@ -13,10 +13,8 @@ /** * Smarty Internal Plugin Compile Registered Function Class - * */ class FunctionCallCompiler extends Base { - /** * Attribute definition: Overwrites base class. * @@ -47,7 +45,6 @@ class FunctionCallCompiler extends Base { */ public function compile($args, Template $compiler, $parameter = [], $tag = null, $function = null): string { - if ($functionHandler = $compiler->getSmarty()->getFunctionHandler($function)) { $attribute_overrides = []; @@ -57,11 +54,11 @@ public function compile($args, Template $compiler, $parameter = [], $tag = null, } // check and get attributes - $_attr = (new AttributesCompiler( + $_attr = (new AttributeCompiler( $attribute_overrides['required_attributes'] ?? $this->required_attributes, - $attribute_overrides['optional_attributes'] ?? $this->optional_attributes, - $attribute_overrides['shorttag_order'] ?? $this->shorttag_order, - $attribute_overrides['option_flags'] ?? $this->option_flags + $attribute_overrides['optional_attributes'] ?? $this->optional_attributes, + $attribute_overrides['shorttag_order'] ?? $this->shorttag_order, + $attribute_overrides['option_flags'] ?? $this->option_flags ))->getAttributes($compiler, $args); unset($_attr['nocache']); @@ -83,5 +80,4 @@ public function compile($args, Template $compiler, $parameter = [], $tag = null, return $output; } - } diff --git a/tests/UnitTests/Compile/AttributeCompilerTest.php b/tests/UnitTests/Compile/AttributeCompilerTest.php new file mode 100644 index 000000000..6be268b91 --- /dev/null +++ b/tests/UnitTests/Compile/AttributeCompilerTest.php @@ -0,0 +1,189 @@ +template_compiler = $this->createMock(Template::class); + + // reset attributes to empty arrays + $this->attributes = [ + 'required_attributes' => [], + 'optional_attributes' => [], + 'shorttag_order' => [], + 'option_flags' => [], + ]; + } + + /** + * Tests shorthand attribute compiling. + */ + public function testAttributeCompiler(): void + { + $this->attributes['shorttag_order'] = ['shorttag']; + $this->attributes['required_attributes'] = ['required']; + $this->attributes['option_flags'] = ['option', 'option_two']; + + $payload = [ + 0 => 'shorttag value', + 1 => [ + 'required' => 'required_value' + ], + 2 => 'option', + ]; + + $this->assertEquals( + (new AttributeCompiler( + ...$this->attributes + ))->getAttributes($this->template_compiler, $payload), + [ + 'shorttag' => 'shorttag value', + 'required' => 'required_value', + 'option' => true, + 'option_two' => false, + ] + ); + } + + /** + * Tests normal optional attribute compiling. + */ + public function testAttributeCompilerOptionalArguments(): void + { + $this->attributes['optional_attributes'] = ['optional']; + + $payload = [ + 0 => [ + 'optional' => 'optional value' + ], + ]; + + $this->assertEquals( + (new AttributeCompiler( + ...$this->attributes + ))->getAttributes($this->template_compiler, $payload), + [ + 'optional' => 'optional value', + ] + ); + + $this->assertEquals( + (new AttributeCompiler( + ...$this->attributes + ))->getAttributes($this->template_compiler, []), + [] + ); + } + + /** + * Tests any attribute compiling. + */ + public function testAttributeCompilerAnyOptionalArguments(): void + { + $this->attributes['optional_attributes'] = ['_any']; + + $payload = [ + 0 => [ + 'optional' => 'optional value' + ], + 1 => [ + 'optional_two' => 'optional value two' + ], + ]; + + $this->assertEquals( + (new AttributeCompiler( + ...$this->attributes + ))->getAttributes($this->template_compiler, $payload), + [ + 'optional' => 'optional value', + 'optional_two' => 'optional value two', + ] + ); + } + + /** + * Test if the attribute compiler tries to throw a too many shorthand attributes error. + */ + public function testAttributeCompilerTooManyShorthands(): void + { + $payload = [ + 0 => 'option one', + ]; + + $this->template_compiler + ->expects(self::once()) + ->method('trigger_template_error') + ->with('too many shorthand attributes', null, true); + + (new AttributeCompiler( + ...$this->attributes + ))->getAttributes($this->template_compiler, $payload); + } + + /** + * Test if the attribute compiler tries to throw a missing required attribute error. + */ + public function testAttributeCompilerWithMissingRequiredAttributes(): void + { + $this->attributes['required_attributes'] = ['required']; + + $this->template_compiler + ->expects(self::once()) + ->method('trigger_template_error') + ->with('missing \'required\' attribute', null, true); + + (new AttributeCompiler( + ...$this->attributes + ))->getAttributes($this->template_compiler, []); + } + + /** + * Test if the attribute compiler tries to throw a illegal value template error. + */ + public function testAttributeCompilerWithInvalidOptionAttribute(): void + { + $this->attributes['option_flags'] = ['option']; + + $this->template_compiler + ->expects(self::once()) + ->method('trigger_template_error') + ->with('illegal value \'\'foo\'\' for options flag \'option\'', null, true); + + (new AttributeCompiler( + ...$this->attributes + ))->getAttributes($this->template_compiler, [0 => ['option' => 'foo']]); + } + + /** + * Test if the attribute compiler tries to throw an unexpected attribute error. + */ + public function testAttributeCompilerWithInvalidUnexpectedAttribute(): void + { + $this->template_compiler + ->expects(self::once()) + ->method('trigger_template_error') + ->with('unexpected \'unexpected\' attribute', null, true); + + (new AttributeCompiler( + ...$this->attributes + ))->getAttributes($this->template_compiler, [0 => ['unexpected' => 'bar']]); + } +} From 833b8e3ae7953a4009271a7e629e0de4c48a7cdd Mon Sep 17 00:00:00 2001 From: Anne Zijlstra Date: Wed, 1 Oct 2025 12:13:39 +0200 Subject: [PATCH 07/10] Make test compatible with php 7.2 --- tests/UnitTests/Compile/AttributeCompilerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/UnitTests/Compile/AttributeCompilerTest.php b/tests/UnitTests/Compile/AttributeCompilerTest.php index 6be268b91..a568bc782 100644 --- a/tests/UnitTests/Compile/AttributeCompilerTest.php +++ b/tests/UnitTests/Compile/AttributeCompilerTest.php @@ -8,12 +8,12 @@ class AttributeCompilerTest extends PHPUnit\Framework\TestCase /** * The template compiler. */ - private Template $template_compiler; + private $template_compiler; /** * The attributes */ - private array $attributes = []; + private $attributes = []; /** * @inheritDoc From 3f5dc0da17ab0cc11f241391d17172db45303979 Mon Sep 17 00:00:00 2001 From: Anne Zijlstra Date: Wed, 1 Oct 2025 12:20:53 +0200 Subject: [PATCH 08/10] Remove splat operator --- .../Compile/AttributeCompilerTest.php | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/tests/UnitTests/Compile/AttributeCompilerTest.php b/tests/UnitTests/Compile/AttributeCompilerTest.php index a568bc782..ca1a5f339 100644 --- a/tests/UnitTests/Compile/AttributeCompilerTest.php +++ b/tests/UnitTests/Compile/AttributeCompilerTest.php @@ -32,6 +32,18 @@ protected function setUp(): void ]; } + /** + * Create the attribute compilor for testing. + */ + private function createAttributeCompiler() { + return new AttributeCompiler( + $this->attributes['required_attributes'], + $this->attributes['optional_attributes'], + $this->attributes['shorttag_order'], + $this->attributes['option_flags'] + ); + } + /** * Tests shorthand attribute compiling. */ @@ -50,9 +62,8 @@ public function testAttributeCompiler(): void ]; $this->assertEquals( - (new AttributeCompiler( - ...$this->attributes - ))->getAttributes($this->template_compiler, $payload), + $this->createAttributeCompiler() + ->getAttributes($this->template_compiler, $payload), [ 'shorttag' => 'shorttag value', 'required' => 'required_value', @@ -76,18 +87,16 @@ public function testAttributeCompilerOptionalArguments(): void ]; $this->assertEquals( - (new AttributeCompiler( - ...$this->attributes - ))->getAttributes($this->template_compiler, $payload), + $this->createAttributeCompiler() + ->getAttributes($this->template_compiler, $payload), [ 'optional' => 'optional value', ] ); $this->assertEquals( - (new AttributeCompiler( - ...$this->attributes - ))->getAttributes($this->template_compiler, []), + $this->createAttributeCompiler() + ->getAttributes($this->template_compiler, []), [] ); } @@ -109,9 +118,8 @@ public function testAttributeCompilerAnyOptionalArguments(): void ]; $this->assertEquals( - (new AttributeCompiler( - ...$this->attributes - ))->getAttributes($this->template_compiler, $payload), + $this->createAttributeCompiler() + ->getAttributes($this->template_compiler, $payload), [ 'optional' => 'optional value', 'optional_two' => 'optional value two', @@ -133,9 +141,8 @@ public function testAttributeCompilerTooManyShorthands(): void ->method('trigger_template_error') ->with('too many shorthand attributes', null, true); - (new AttributeCompiler( - ...$this->attributes - ))->getAttributes($this->template_compiler, $payload); + $this->createAttributeCompiler() + ->getAttributes($this->template_compiler, $payload); } /** @@ -150,9 +157,8 @@ public function testAttributeCompilerWithMissingRequiredAttributes(): void ->method('trigger_template_error') ->with('missing \'required\' attribute', null, true); - (new AttributeCompiler( - ...$this->attributes - ))->getAttributes($this->template_compiler, []); + $this->createAttributeCompiler() + ->getAttributes($this->template_compiler, []); } /** @@ -182,8 +188,7 @@ public function testAttributeCompilerWithInvalidUnexpectedAttribute(): void ->method('trigger_template_error') ->with('unexpected \'unexpected\' attribute', null, true); - (new AttributeCompiler( - ...$this->attributes - ))->getAttributes($this->template_compiler, [0 => ['unexpected' => 'bar']]); + $this->createAttributeCompiler() + ->getAttributes($this->template_compiler, [0 => ['unexpected' => 'bar']]); } } From 3a12cd4585837d5bfaf8b9101714d480c04dc662 Mon Sep 17 00:00:00 2001 From: Anne Zijlstra Date: Wed, 1 Oct 2025 12:23:33 +0200 Subject: [PATCH 09/10] Remove last splat operator --- tests/UnitTests/Compile/AttributeCompilerTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/UnitTests/Compile/AttributeCompilerTest.php b/tests/UnitTests/Compile/AttributeCompilerTest.php index ca1a5f339..57725360f 100644 --- a/tests/UnitTests/Compile/AttributeCompilerTest.php +++ b/tests/UnitTests/Compile/AttributeCompilerTest.php @@ -173,9 +173,8 @@ public function testAttributeCompilerWithInvalidOptionAttribute(): void ->method('trigger_template_error') ->with('illegal value \'\'foo\'\' for options flag \'option\'', null, true); - (new AttributeCompiler( - ...$this->attributes - ))->getAttributes($this->template_compiler, [0 => ['option' => 'foo']]); + $this->createAttributeCompiler() + ->getAttributes($this->template_compiler, [0 => ['option' => 'foo']]); } /** From 7823133865e69fd3105276a1ed845041c38e4f43 Mon Sep 17 00:00:00 2001 From: Anne Zijlstra Date: Wed, 1 Oct 2025 13:28:02 +0200 Subject: [PATCH 10/10] Add unittests for attrivbute function handlers --- .../Compile/AttributeCompilerTest.php | 2 +- .../Compile/FunctionCallCompilerTest.php | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/UnitTests/Compile/FunctionCallCompilerTest.php diff --git a/tests/UnitTests/Compile/AttributeCompilerTest.php b/tests/UnitTests/Compile/AttributeCompilerTest.php index 57725360f..9a8fbf98a 100644 --- a/tests/UnitTests/Compile/AttributeCompilerTest.php +++ b/tests/UnitTests/Compile/AttributeCompilerTest.php @@ -33,7 +33,7 @@ protected function setUp(): void } /** - * Create the attribute compilor for testing. + * Create the attribute compiler for testing. */ private function createAttributeCompiler() { return new AttributeCompiler( diff --git a/tests/UnitTests/Compile/FunctionCallCompilerTest.php b/tests/UnitTests/Compile/FunctionCallCompilerTest.php new file mode 100644 index 000000000..2eb0e5a1a --- /dev/null +++ b/tests/UnitTests/Compile/FunctionCallCompilerTest.php @@ -0,0 +1,62 @@ +smarty = $this->createMock(Smarty::class); + $this->template_compiler = $this->createMock(Template::class); + $this->template_compiler + ->expects(self::once()) + ->method('getSmarty') + ->willReturn($this->smarty); + } + + public function testAttributeFunctionHandlerInterface(): void + { + $attribute_function_handler = $this->createMock(AttributeFunctionHandlerInterface::class); + + $attribute_function_handler + ->expects(self::once()) + ->method('getSupportedAttributes') + ->willReturn([ + 'required_attributes' => ['required'], + 'optional_attributes' => ['optional'], + 'shorttag_order' => ['short'], + 'option_flags' => ['option'], + ]); + + $args = [ + 0 => 'short', + 1 => 'option', + 2 => [ + 'optional' => 'optional', + ], + 3 => [ + 'required' => 'required', + ], + ]; + + $this->smarty + ->expects(self::once()) + ->method('getFunctionHandler') + ->with('method') + ->willReturn($attribute_function_handler); + + $function_call_compiler = new FunctionCallCompiler(); + + $this->assertEquals( + $function_call_compiler->compile($args, $this->template_compiler, [], null, 'method'), + '$_smarty_tpl->getSmarty()->getFunctionHandler(\'method\')->handle(array(\'short\'=>short,\'option\'=>1,\'optional\'=>optional,\'required\'=>required), $_smarty_tpl)' + ); + } +}