diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 1242c92fcc..9eeb65fe31 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.25 under development ------------------------ +- Bug #17223: Fixed detaching a behavior event when it is a Closure instance (GHopperMSK, rob006) - Bug #15779: If directory path is passed to `FileHelper::unlink()` and directory has files it will not delete files in this directory on Windows now (alexkart) - Bug #17473: Fixed `SimpleConditionBuilder::build()` when column is not a string (alexkart) - Bug #17486: Fixed error when using `batch()` without `$db` parameter with MSSQL (alexkart) diff --git a/framework/base/Behavior.php b/framework/base/Behavior.php index e1498b913a..95e475707c 100644 --- a/framework/base/Behavior.php +++ b/framework/base/Behavior.php @@ -27,6 +27,10 @@ class Behavior extends BaseObject */ public $owner; + /** + * @var Array Attached events handlers + */ + private $_attachedEvents = []; /** * Declares event handlers for the [[owner]]'s events. @@ -72,6 +76,7 @@ class Behavior extends BaseObject { $this->owner = $owner; foreach ($this->events() as $event => $handler) { + $this->_attachedEvents[$event] = $handler; $owner->on($event, is_string($handler) ? [$this, $handler] : $handler); } } @@ -85,9 +90,10 @@ class Behavior extends BaseObject public function detach() { if ($this->owner) { - foreach ($this->events() as $event => $handler) { + foreach ($this->_attachedEvents as $event => $handler) { $this->owner->off($event, is_string($handler) ? [$this, $handler] : $handler); } + $this->_attachedEvents = []; $this->owner = null; } } diff --git a/tests/framework/base/ComponentTest.php b/tests/framework/base/ComponentTest.php index e9f74a3223..cea50aac54 100644 --- a/tests/framework/base/ComponentTest.php +++ b/tests/framework/base/ComponentTest.php @@ -445,6 +445,25 @@ class ComponentTest extends TestCase $this->assertFalse($obj->off('test', [$this, 'handler2']), 'Trying to remove the handler that is not attached'); $this->assertTrue($obj->off('test', [$this, 'handler']), 'Trying to remove the attached handler'); } + + /** + * @see https://github.com/yiisoft/yii2/issues/17223 + */ + public function testEventClosureDetachesItself() + { + if (PHP_VERSION_ID < 70000) { + $this->markTestSkipped('Can not be tested on PHP < 7.0'); + return; + } + + $obj = require __DIR__ . '/stub/AnonymousComponentClass.php'; + + $obj->trigger('barEventOnce'); + $this->assertEquals(1, $obj->foo); + $obj->trigger('barEventOnce'); + $this->assertEquals(1, $obj->foo); + } + } class NewComponent extends Component diff --git a/tests/framework/base/ModelTest.php b/tests/framework/base/ModelTest.php index b9dc899b1b..2e3c082562 100644 --- a/tests/framework/base/ModelTest.php +++ b/tests/framework/base/ModelTest.php @@ -494,7 +494,7 @@ class ModelTest extends TestCase return; } - $model = include 'stub/AnonymousModelClass.php'; + $model = require __DIR__ . '/stub/AnonymousModelClass.php'; $this->expectException('yii\base\InvalidConfigException'); $this->expectExceptionMessage('The "formName()" method should be explicitly defined for anonymous models'); diff --git a/tests/framework/base/stub/AnonymousComponentClass.php b/tests/framework/base/stub/AnonymousComponentClass.php new file mode 100644 index 0000000000..d711cac37b --- /dev/null +++ b/tests/framework/base/stub/AnonymousComponentClass.php @@ -0,0 +1,21 @@ +attachBehavior('bar', (new class () extends \yii\base\Behavior +{ + public function events() + { + return [ + 'barEventOnce' => function ($event) { + $this->owner->foo++; + $this->detach(); + }, + ]; + } +})); + +return $obj;