From 8526a15029ca108537d42518039965bf125f2aa6 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Fri, 5 Apr 2024 23:41:23 -0400 Subject: [PATCH 1/2] Add `assignByRef` support --- src/Data.php | 56 +++++++++++++++ src/Variable.php | 9 +++ .../AssignByRef/AssignByRefTest.php | 70 +++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 tests/UnitTests/SmartyMethodsTests/AssignByRef/AssignByRefTest.php diff --git a/src/Data.php b/src/Data.php index 6ae823d8c..f2bc5db5f 100644 --- a/src/Data.php +++ b/src/Data.php @@ -153,6 +153,62 @@ public function assign($tpl_var, $value = null, $nocache = false, $scope = null) return $this; } + /** + * Assigns a Smarty variable by reference + * + * @param string $tpl_var the template variable name + * @param mixed $value the value (by reference) to assign + * @param boolean $nocache if true any output of this variable will be not cached + * @param int $scope one of self::SCOPE_* constants + * + * @return Data current Data (or Smarty or \Smarty\Template) instance for + * chaining + */ + public function assignByRef($tpl_var, &$value, $nocache = false, $scope = null) + { + switch ($scope ?? $this->getDefaultScope()) { + case self::SCOPE_GLOBAL: + case self::SCOPE_SMARTY: + $this->getSmarty()->assignByRef($tpl_var, $value); + break; + case self::SCOPE_TPL_ROOT: + $ptr = $this; + while (isset($ptr->parent) && ($ptr->parent instanceof Template)) { + $ptr = $ptr->parent; + } + $ptr->assignByRef($tpl_var, $value); + break; + case self::SCOPE_ROOT: + $ptr = $this; + while (isset($ptr->parent) && !($ptr->parent instanceof Smarty)) { + $ptr = $ptr->parent; + } + $ptr->assignByRef($tpl_var, $value); + break; + case self::SCOPE_PARENT: + if ($this->parent) { + $this->parent->assignByRef($tpl_var, $value); + } else { + // assign local as fallback + $this->assignByRef($tpl_var, $value); + } + break; + case self::SCOPE_LOCAL: + default: + if (isset($this->tpl_vars[$tpl_var])) { + $this->tpl_vars[$tpl_var]->setValueByRef($value); + if ($nocache) { + $this->tpl_vars[$tpl_var]->setNocache(true); + } + } else { + $this->tpl_vars[$tpl_var] = new Variable(null, $nocache); + $this->tpl_vars[$tpl_var]->setValueByRef($value); + } + } + + return $this; + } + /** * appends values to template variables * diff --git a/src/Variable.php b/src/Variable.php index 0e38d1257..2be7a0f70 100644 --- a/src/Variable.php +++ b/src/Variable.php @@ -31,6 +31,15 @@ public function setValue($value): void { $this->value = $value; } + /** + * Set value by reference + * + * @param mixed|null $value + */ + public function setValueByRef(&$value): void { + $this->value = &$value; + } + /** * if true any output of this variable will be not cached * diff --git a/tests/UnitTests/SmartyMethodsTests/AssignByRef/AssignByRefTest.php b/tests/UnitTests/SmartyMethodsTests/AssignByRef/AssignByRefTest.php new file mode 100644 index 000000000..562eea7bf --- /dev/null +++ b/tests/UnitTests/SmartyMethodsTests/AssignByRef/AssignByRefTest.php @@ -0,0 +1,70 @@ +setUpSmarty(__DIR__); + } + + public function testInit() + { + $this->cleanDirs(); + } + + private $testStr = null; + /** + * Test assignByRef for nullable string property + */ + public function testAssignByRefForNullableStringProperry() + { + $this->smarty->assignByRef('myVar', $this->testStr); + $this->assertEquals(null, $this->smarty->fetch('eval:{$myVar}')); + $this->testStr = 'abc'; + $this->assertEquals('abc', $this->smarty->fetch('eval:{$myVar}')); + } + + /** + * Test assignByRef for string + */ + public function testAssignByRefForString() + { + $var = 'abc'; + $this->smarty->assignByRef('myVar', $var); + $this->assertEquals('abc', $this->smarty->fetch('eval:{$myVar}')); + $var = 'def'; + $this->assertEquals('def', $this->smarty->fetch('eval:{$myVar}')); + } + + /** + * Test assignByRef for array + */ + public function testAssignByRefForArray() + { + $var = array( + 'a' => 'A', + ); + $this->smarty->assignByRef('myVar', $var); + $this->assertEquals('{"a":"A"}', $this->smarty->fetch('eval:{$myVar|json_encode}')); + $var['b'] = 'B'; + $this->assertEquals('{"a":"A","b":"B"}', $this->smarty->fetch('eval:{$myVar|json_encode}')); + } + + /** + * Test that assignByRef returns this. + */ + public function testAssignByRefReturnsThis() + { + $var = 'data'; + $this->assertEquals( + 'data', + $this->smarty->assignByRef('dummy', $var)->fetch('eval:{$dummy}') + ); + } +} From 69b547765b01808ec63b90604257a57af6218cd0 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Sat, 6 Apr 2024 11:48:23 -0400 Subject: [PATCH 2/2] Add more tests --- .../AssignByRef/AssignByRefTest.php | 188 +++++++++++++++++- 1 file changed, 187 insertions(+), 1 deletion(-) diff --git a/tests/UnitTests/SmartyMethodsTests/AssignByRef/AssignByRefTest.php b/tests/UnitTests/SmartyMethodsTests/AssignByRef/AssignByRefTest.php index 562eea7bf..fb78240ae 100644 --- a/tests/UnitTests/SmartyMethodsTests/AssignByRef/AssignByRefTest.php +++ b/tests/UnitTests/SmartyMethodsTests/AssignByRef/AssignByRefTest.php @@ -3,6 +3,8 @@ * Smarty PHPunit tests assignByRef method */ +use Smarty\Data; + /** * Class for assignByRef tests */ @@ -22,7 +24,7 @@ public function testInit() /** * Test assignByRef for nullable string property */ - public function testAssignByRefForNullableStringProperry() + public function testAssignByRefForNullableStringProperty() { $this->smarty->assignByRef('myVar', $this->testStr); $this->assertEquals(null, $this->smarty->fetch('eval:{$myVar}')); @@ -67,4 +69,188 @@ public function testAssignByRefReturnsThis() $this->smarty->assignByRef('dummy', $var)->fetch('eval:{$dummy}') ); } + + /** + * Test duplicate assignByRef + */ + public function testDuplicateAssignByRef() + { + $var1 = array( + 'a' => 'A', + ); + $var2= array( + 'z' => 'Z', + ); + $this->smarty->assignByRef('myVar', $var1); + $this->smarty->assignByRef('myVar', $var2); + $this->assertEquals('{"z":"Z"}', $this->smarty->fetch('eval:{$myVar|json_encode}')); + $var1['b'] = 'B'; + $var2['y'] = 'Y'; + $this->assertEquals('{"z":"Z","y":"Y"}', $this->smarty->fetch('eval:{$myVar|json_encode}')); + } + + /** + * Test assignByRef for parent scope + */ + public function testAssignByRefForParentScope() + { + $data1 = $this->smarty->createData($this->smarty); + $data2 = $this->smarty->createData($data1); + $var1 = array( + 'a1' => 'A1' + ); + $var2 = array( + 'b1' => 'B1' + ); + $var3 = array( + 'c1' => 'C1' + ); + $data1->assignByRef('var1', $var1); + $data2->assignByRef('var2', $var2, false, Data::SCOPE_PARENT); + $data2->assignByRef('var3', $var3); + $this->assertEquals($var1, $data2->getTemplateVars('var1')); + $this->assertEquals(null, $data2->getTemplateVars('var1', false)); + $this->assertEquals($var2, $data2->getTemplateVars('var2')); + $this->assertEquals(null, $data2->getTemplateVars('var2', false)); + $this->assertEquals($var3, $data2->getTemplateVars('var3')); + $this->assertEquals($var3, $data2->getTemplateVars('var3', false)); + + $var1['a2'] = 'A2'; + $var2['b2'] = 'B2'; + $var3['c2'] = 'C2'; + $this->assertEquals($var1, $data2->getTemplateVars('var1')); + $this->assertEquals(null, $data2->getTemplateVars('var1', false)); + $this->assertEquals($var2, $data2->getTemplateVars('var2')); + $this->assertEquals(null, $data2->getTemplateVars('var2', false)); + $this->assertEquals($var3, $data2->getTemplateVars('var3')); + $this->assertEquals($var3, $data2->getTemplateVars('var3', false)); + } + + /** + * Test assignByRef for root scope + */ + public function testAssignByRefForRootScope() + { + $data1 = $this->smarty->createData($this->smarty); + $data2 = $this->smarty->createData($data1); + $data3 = $this->smarty->createData($data2); + $var1 = array( + 'a1' => 'A1' + ); + $var2 = array( + 'b1' => 'B1' + ); + $var3 = array( + 'c1' => 'C1' + ); + $data1->assignByRef('var1', $var1); + $data2->assignByRef('var2', $var2, false, Data::SCOPE_PARENT); + $data3->assignByRef('var3', $var3, false, Data::SCOPE_ROOT); + $this->assertEquals($var3, $data1->getTemplateVars('var3')); + $this->assertEquals($var3, $data1->getTemplateVars('var3', false)); + $this->assertEquals($var1, $data2->getTemplateVars('var1')); + $this->assertEquals(null, $data2->getTemplateVars('var1', false)); + $this->assertEquals($var2, $data2->getTemplateVars('var2')); + $this->assertEquals(null, $data2->getTemplateVars('var2', false)); + $this->assertEquals($var3, $data2->getTemplateVars('var3')); + $this->assertEquals(null, $data2->getTemplateVars('var3', false)); + + $var1['a2'] = 'A2'; + $var2['b2'] = 'B2'; + $var3['c2'] = 'C2'; + $this->assertEquals($var3, $data1->getTemplateVars('var3')); + $this->assertEquals($var3, $data1->getTemplateVars('var3', false)); + $this->assertEquals($var1, $data2->getTemplateVars('var1')); + $this->assertEquals(null, $data2->getTemplateVars('var1', false)); + $this->assertEquals($var2, $data2->getTemplateVars('var2')); + $this->assertEquals(null, $data2->getTemplateVars('var2', false)); + $this->assertEquals($var3, $data2->getTemplateVars('var3')); + $this->assertEquals(null, $data2->getTemplateVars('var3', false)); + } + + /** + * Test assignByRef for TPL global scope + */ + public function testAssignByRefForGlobalScope() + { + $data1 = $this->smarty->createData($this->smarty); + $data2 = $this->smarty->createData($data1); + $data3 = $this->smarty->createData($data2); + $var1 = array( + 'a1' => 'A1' + ); + $var2 = array( + 'b1' => 'B1' + ); + $var3 = array( + 'c1' => 'C1' + ); + $data1->assignByRef('var1', $var1); + $data2->assignByRef('var2', $var2, false, Data::SCOPE_PARENT); + $data3->assignByRef('var3', $var3, false, Data::SCOPE_GLOBAL); + $this->assertEquals($var3, $this->smarty->getTemplateVars('var3')); + $this->assertEquals($var3, $this->smarty->getTemplateVars('var3', false)); + $this->assertEquals($var3, $data1->getTemplateVars('var3')); + $this->assertEquals(null, $data1->getTemplateVars('var3', false)); + $this->assertEquals($var1, $data2->getTemplateVars('var1')); + $this->assertEquals(null, $data2->getTemplateVars('var1', false)); + $this->assertEquals($var2, $data2->getTemplateVars('var2')); + $this->assertEquals(null, $data2->getTemplateVars('var2', false)); + $this->assertEquals($var3, $data2->getTemplateVars('var3')); + $this->assertEquals(null, $data2->getTemplateVars('var3', false)); + + $var1['a2'] = 'A2'; + $var2['b2'] = 'B2'; + $var3['c2'] = 'C2'; + $this->assertEquals($var3, $this->smarty->getTemplateVars('var3')); + $this->assertEquals($var3, $this->smarty->getTemplateVars('var3', false)); + $this->assertEquals($var3, $data1->getTemplateVars('var3')); + $this->assertEquals(null, $data1->getTemplateVars('var3', false)); + $this->assertEquals($var1, $data2->getTemplateVars('var1')); + $this->assertEquals(null, $data2->getTemplateVars('var1', false)); + $this->assertEquals($var2, $data2->getTemplateVars('var2')); + $this->assertEquals(null, $data2->getTemplateVars('var2', false)); + $this->assertEquals($var3, $data2->getTemplateVars('var3')); + $this->assertEquals(null, $data2->getTemplateVars('var3', false)); + } + + /** + * Test assignByRef for TPL root scope + */ + public function testAssignByRefForTplRootScope() + { + $data1 = $this->smarty->createData($this->smarty); + $tpl1 = $this->smarty->createTemplate('eval:{$var2|json_encode}', $data1); + $tpl2 = $this->smarty->createTemplate('eval:{$var2|json_encode}', $tpl1); + $data2 = $tpl2->createData($tpl2); + $var1 = array( + 'a1' => 'A1' + ); + $var2 = array( + 'b1' => 'B1' + ); + $var3 = array( + 'c1' => 'C1' + ); + $data1->assignByRef('var1', $var1); + $data2->assignByRef('var2', $var2, false, Data::SCOPE_TPL_ROOT); + $this->assertEquals('{"b1":"B1"}', $tpl1->fetch()); + $this->assertEquals($var2, $tpl1->getTemplateVars('var2')); + $this->assertEquals($var2, $tpl1->getTemplateVars('var2', false)); + $this->assertEquals(null, $data1->getTemplateVars('var2')); + $this->assertEquals($var1, $data2->getTemplateVars('var1')); + $this->assertEquals(null, $data2->getTemplateVars('var1', false)); + $this->assertEquals($var2, $data2->getTemplateVars('var2')); + $this->assertEquals(null, $data2->getTemplateVars('var2', false)); + + $var1['a2'] = 'A2'; + $var2['b2'] = 'B2'; + $this->assertEquals($var2, $tpl1->getTemplateVars('var2')); + $this->assertEquals($var2, $tpl1->getTemplateVars('var2', false)); + $this->assertEquals(null, $data1->getTemplateVars('var2')); + $this->assertEquals($var1, $data2->getTemplateVars('var1')); + $this->assertEquals(null, $data2->getTemplateVars('var1', false)); + $this->assertEquals($var2, $data2->getTemplateVars('var2')); + $this->assertEquals(null, $data2->getTemplateVars('var2', false)); + } }