diff --git a/src/Attributes.php b/src/Attributes.php index ae15ef88..ffe22eea 100644 --- a/src/Attributes.php +++ b/src/Attributes.php @@ -365,29 +365,19 @@ public function setPrefix($prefix) /** * Register callback for an attribute * - * @param string $name Name of the attribute to register the callback for - * @param callable $callback Callback to call when retrieving the attribute - * @param callable $setterCallback Callback to call when setting the attribute + * @param string $name Name of the attribute to register the callback for + * @param ?callable $callback Callback to call when retrieving the attribute + * @param ?callable $setterCallback Callback to call when setting the attribute * * @return $this - * - * @throws InvalidArgumentException If $callback is not callable or if $setterCallback is set and not callable */ - public function registerAttributeCallback($name, $callback, $setterCallback = null) + public function registerAttributeCallback(string $name, ?callable $callback, ?callable $setterCallback = null): self { if ($callback !== null) { - if (! is_callable($callback)) { - throw new InvalidArgumentException(__METHOD__ . ' expects a callable callback'); - } - $this->callbacks[$name] = $callback; } if ($setterCallback !== null) { - if (! is_callable($setterCallback)) { - throw new InvalidArgumentException(__METHOD__ . ' expects a callable setterCallback'); - } - $this->setterCallbacks[$name] = $setterCallback; } @@ -518,4 +508,11 @@ public function getIterator(): Traversable { return new ArrayIterator($this->attributes); } + + public function __clone() + { + foreach ($this->attributes as &$attribute) { + $attribute = clone $attribute; + } + } } diff --git a/src/BaseHtmlElement.php b/src/BaseHtmlElement.php index da8348d0..41b6e4b5 100644 --- a/src/BaseHtmlElement.php +++ b/src/BaseHtmlElement.php @@ -352,4 +352,13 @@ public function renderUnwrapped() $tag ); } + + public function __clone() + { + parent::__clone(); + + if ($this->attributes !== null) { + $this->attributes = clone $this->attributes; + } + } } diff --git a/tests/AttributesTest.php b/tests/AttributesTest.php index 51d2c4a2..8ce23e8b 100644 --- a/tests/AttributesTest.php +++ b/tests/AttributesTest.php @@ -2,8 +2,11 @@ namespace ipl\Tests\Html; +use ipl\Html\Attribute; use ipl\Html\Attributes; use ipl\Html\BaseHtmlElement; +use ipl\Html\HtmlString; +use ipl\Html\ValidHtml; class AttributesTest extends TestCase { @@ -113,4 +116,46 @@ function ($v) use (&$value) { $this->assertEquals(' foo="bar rab" bar="foo"', $attributes->render()); } + + public function testClone(): void + { + $original = Attributes::create([ + 'class' => 'original-class', + 'value' => 'original-value' + ]); + + $clone = clone $original; + $clone->get('class')->setValue('clone-class'); + $clone->get('name')->setValue('clone-name'); + $clone->remove('value'); + + $cloneCone = clone $clone; + $cloneCone->get('class')->addValue('clone-clone-class'); + $cloneCone->get('name')->setValue('clone-clone-name'); + $cloneCone->get('value')->setValue('clone-clone-value'); + + $this->assertSame('original-class', $original->get('class')->getValue()); + $this->assertSame('original-value', $original->get('value')->getValue()); + $this->assertNull($original->get('name')->getValue()); + $this->assertSame( + ' class="original-class" value="original-value"', + $original->render() + ); + + $this->assertSame('clone-class', $clone->get('class')->getValue()); + $this->assertNull($clone->get('value')->getValue()); + $this->assertSame('clone-name', $clone->get('name')->getValue()); + $this->assertSame( + ' class="clone-class" name="clone-name"', + $clone->render() + ); + + $this->assertSame(['clone-class', 'clone-clone-class'], $cloneCone->get('class')->getValue()); + $this->assertSame('clone-clone-name', $cloneCone->get('name')->getValue()); + $this->assertSame('clone-clone-value', $cloneCone->get('value')->getValue()); + $this->assertSame( + ' class="clone-class clone-clone-class" name="clone-clone-name" value="clone-clone-value"', + $cloneCone->render() + ); + } }