diff --git a/framework/base/Component.php b/framework/base/Component.php index 34977b1..e7cf72a 100644 --- a/framework/base/Component.php +++ b/framework/base/Component.php @@ -14,6 +14,8 @@ namespace yii\base; * * @include @yii/docs/base-Component.md * + * @property Behavior[] behaviors list of behaviors currently attached to this component + * * @author Qiang Xue * @since 2.0 */ @@ -39,7 +41,7 @@ class Component extends \yii\base\Object * will be implicitly called when executing `$value = $component->property;`. * @param string $name the property name * @return mixed the property value, event handlers attached to the event, - * the named behavior, or the value of a behavior's property + * the behavior, or the value of a behavior's property * @throws BadPropertyException if the property is not defined * @see __set */ @@ -52,8 +54,8 @@ class Component extends \yii\base\Object } else { // behavior property $this->ensureBehaviors(); - foreach ($this->_b as $i => $behavior) { - if (is_string($i) && $behavior->canGetProperty($name)) { + foreach ($this->_b as $behavior) { + if ($behavior->canGetProperty($name)) { return $behavior->$name; } } @@ -96,8 +98,8 @@ class Component extends \yii\base\Object } else { // behavior property $this->ensureBehaviors(); - foreach ($this->_b as $i => $behavior) { - if (is_string($i) && $behavior->canSetProperty($name)) { + foreach ($this->_b as $behavior) { + if ($behavior->canSetProperty($name)) { $behavior->$name = $value; return; } @@ -126,13 +128,12 @@ class Component extends \yii\base\Object { $getter = 'get' . $name; if (method_exists($this, $getter)) { - // property is not null return $this->$getter() !== null; } else { // behavior property $this->ensureBehaviors(); - foreach ($this->_b as $i => $behavior) { - if (is_string($i) && $behavior->canGetProperty($name)) { + foreach ($this->_b as $behavior) { + if ($behavior->canGetProperty($name)) { return $behavior->$name !== null; } } @@ -156,14 +157,13 @@ class Component extends \yii\base\Object { $setter = 'set' . $name; if (method_exists($this, $setter)) { - // write property $this->$setter(null); return; } else { // behavior property $this->ensureBehaviors(); - foreach ($this->_b as $i => $behavior) { - if (is_string($i) && $behavior->canSetProperty($name)) { + foreach ($this->_b as $behavior) { + if ($behavior->canSetProperty($name)) { $behavior->$name = null; return; } @@ -190,16 +190,17 @@ class Component extends \yii\base\Object */ public function __call($name, $params) { - if ($this->canGetProperty($name, false)) { - $func = $this->$name; + $getter = 'get' . $name; + if (method_exists($this, $getter)) { + $func = $this->$getter(); if ($func instanceof \Closure) { return call_user_func_array($func, $params); } } $this->ensureBehaviors(); - foreach ($this->_b as $i => $object) { - if (is_string($i) && method_exists($object, $name)) { + foreach ($this->_b as $object) { + if (method_exists($object, $name)) { return call_user_func_array(array($object, $name), $params); } } @@ -213,10 +214,92 @@ class Component extends \yii\base\Object */ public function __clone() { + $this->_e = null; $this->_b = null; } /** + * Returns a value indicating whether a property is defined for this component. + * A property is defined if: + * + * - the class has a getter or setter method associated with the specified name + * (in this case, property name is case-insensitive); + * - the class has a member variable with the specified name (when `$checkVar` is true); + * - an attached behavior has a property of the given name (when `$checkBehavior` is true). + * + * @param string $name the property name + * @param boolean $checkVar whether to treat member variables as properties + * @param boolean $checkBehavior whether to treat behaviors' properties as properties of this component + * @return boolean whether the property is defined + * @see canGetProperty + * @see canSetProperty + */ + public function hasProperty($name, $checkVar = true, $checkBehavior = true) + { + return $this->canGetProperty($name, $checkVar, $checkBehavior) || $this->canSetProperty($name, $checkVar, $checkBehavior); + } + + /** + * Returns a value indicating whether a property can be read. + * A property can be read if: + * + * - the class has a getter method associated with the specified name + * (in this case, property name is case-insensitive); + * - the class has a member variable with the specified name (when `$checkVar` is true); + * - an attached behavior has a readable property of the given name (when `$checkBehavior` is true). + * + * @param string $name the property name + * @param boolean $checkVar whether to treat member variables as properties + * @param boolean $checkBehavior whether to treat behaviors' properties as properties of this component + * @return boolean whether the property can be read + * @see canSetProperty + */ + public function canGetProperty($name, $checkVar = true, $checkBehavior = true) + { + if (method_exists($this, 'get' . $name) || $checkVar && property_exists($this, $name)) { + return true; + } else { + $this->ensureBehaviors(); + foreach ($this->_b as $behavior) { + if ($behavior->canGetProperty($name, $checkVar)) { + return true; + } + } + return false; + } + } + + /** + * Returns a value indicating whether a property can be set. + * A property can be written if: + * + * - the class has a setter method associated with the specified name + * (in this case, property name is case-insensitive); + * - the class has a member variable with the specified name (when `$checkVar` is true); + * - an attached behavior has a writable property of the given name (when `$checkBehavior` is true). + * + * @param string $name the property name + * @param boolean $checkVar whether to treat member variables as properties + * @param boolean $checkBehavior whether to treat behaviors' properties as properties of this component + * @return boolean whether the property can be written + * @see canGetProperty + */ + public function canSetProperty($name, $checkVar = true, $checkBehavior = true) + { + if (method_exists($this, 'set' . $name) || $checkVar && property_exists($this, $name)) { + return true; + } else { + $this->ensureBehaviors(); + foreach ($this->_b as $behavior) { + if ($behavior->canSetProperty($name, $checkVar)) { + return true; + } + } + return false; + } + } + + /** * Returns a list of behaviors that this component should behave as. * * Child classes may override this method to specify the behaviors they want to behave as. @@ -269,7 +352,6 @@ class Component extends \yii\base\Object * * @param string $name the event name * @return Vector list of attached event handlers for the event - * @throws Exception if the event is not defined */ public function getEventHandlers($name) { @@ -309,7 +391,7 @@ class Component extends \yii\base\Object * * @param string $name the event name * @param string|array|\Closure $handler the event handler - * @see off + * @see off() */ public function on($name, $handler) { @@ -317,12 +399,12 @@ class Component extends \yii\base\Object } /** - * Detaches an existing event handler. - * This method is the opposite of [[on]]. + * Detaches an existing event handler from this component. + * This method is the opposite of [[on()]]. * @param string $name event name * @param string|array|\Closure $handler the event handler to be removed * @return boolean if a handler is found and detached - * @see on + * @see on() */ public function off($name, $handler) { @@ -335,7 +417,6 @@ class Component extends \yii\base\Object * all attached handlers for the event. * @param string $name the event name * @param Event $event the event parameter. If not set, a default [[Event]] object will be created. - * @throws Exception if the event is undefined or an event handler is invalid. */ public function trigger($name, $event = null) { @@ -344,10 +425,8 @@ class Component extends \yii\base\Object if ($event === null) { $event = new Event($this); } - if ($event instanceof Event) { - $event->handled = false; - $event->name = $name; - } + $event->handled = false; + $event->name = $name; foreach ($this->_e[$name] as $handler) { call_user_func($handler, $event); // stop further handling if the event is handled @@ -384,9 +463,7 @@ class Component extends \yii\base\Object * This method will create the behavior object based on the given * configuration. After that, the behavior object will be attached to * this component by calling the [[Behavior::attach]] method. - * @param integer|string $name the name of the behavior. This can be a string or an integer (or empty string). - * If the former, it uniquely identifies this behavior. If the latter, the behavior becomes - * anonymous and its methods and properties will NOT be made available in this component. + * @param string $name the name of the behavior. * @param string|array|Behavior $behavior the behavior configuration. This can be one of the following: * * - a [[Behavior]] object @@ -425,7 +502,6 @@ class Component extends \yii\base\Object */ public function detachBehavior($name) { - $this->ensureBehaviors(); if (isset($this->_b[$name])) { $behavior = $this->_b[$name]; unset($this->_b[$name]); @@ -464,8 +540,7 @@ class Component extends \yii\base\Object /** * Attaches a behavior to this component. - * @param integer|string $name the name of the behavior. If it is an integer or an empty string, - * the behavior is anonymous and its methods and properties will NOT be made available to the owner component. + * @param string $name the name of the behavior. * @param string|array|Behavior $behavior the behavior to be attached * @return Behavior the attached behavior. */ @@ -474,16 +549,10 @@ class Component extends \yii\base\Object if (!($behavior instanceof Behavior)) { $behavior = \Yii::createObject($behavior); } - if (is_int($name) || $name == '') { - // anonymous behavior - $behavior->attach($this); - return $this->_b[] = $behavior; - } else { - if (isset($this->_b[$name])) { - $this->_b[$name]->detach($this); - } - $behavior->attach($this); - return $this->_b[$name] = $behavior; + if (isset($this->_b[$name])) { + $this->_b[$name]->detach($this); } + $behavior->attach($this); + return $this->_b[$name] = $behavior; } } diff --git a/framework/base/Object.php b/framework/base/Object.php index 7d75b49..15723dc 100644 --- a/framework/base/Object.php +++ b/framework/base/Object.php @@ -160,8 +160,12 @@ class Object /** * Returns a value indicating whether a property is defined. - * A property is defined if there is a getter or setter method - * defined in the class. Note that property names are case-insensitive. + * A property is defined if: + * + * - the class has a getter or setter method associated with the specified name + * (in this case, property name is case-insensitive); + * - the class has a member variable with the specified name (when `$checkVar` is true); + * * @param string $name the property name * @param boolean $checkVar whether to treat member variables as properties * @return boolean whether the property is defined @@ -175,8 +179,12 @@ class Object /** * Returns a value indicating whether a property can be read. - * A property can be read if the class has a getter method - * for the property name. Note that property name is case-insensitive. + * A property is readable if: + * + * - the class has a getter method associated with the specified name + * (in this case, property name is case-insensitive); + * - the class has a member variable with the specified name (when `$checkVar` is true); + * * @param string $name the property name * @param boolean $checkVar whether to treat member variables as properties * @return boolean whether the property can be read @@ -189,8 +197,12 @@ class Object /** * Returns a value indicating whether a property can be set. - * A property can be written if the class has a setter method - * for the property name. Note that property name is case-insensitive. + * A property is writable if: + * + * - the class has a setter method associated with the specified name + * (in this case, property name is case-insensitive); + * - the class has a member variable with the specified name (when `$checkVar` is true); + * * @param string $name the property name * @param boolean $checkVar whether to treat member variables as properties * @return boolean whether the property can be written diff --git a/tests/unit/framework/base/ComponentTest.php b/tests/unit/framework/base/ComponentTest.php index 60d5f7c..6729af7 100644 --- a/tests/unit/framework/base/ComponentTest.php +++ b/tests/unit/framework/base/ComponentTest.php @@ -29,12 +29,15 @@ class ComponentTest extends \yiiunit\TestCase { $this->component = null; } - + public function testHasProperty() { - $this->assertTrue($this->component->hasProperty('Text'), "Component hasn't property Text"); - $this->assertTrue($this->component->hasProperty('text'), "Component hasn't property text"); - $this->assertFalse($this->component->hasProperty('Caption'), "Component as property Caption"); + $this->assertTrue($this->component->hasProperty('Text')); + $this->assertTrue($this->component->hasProperty('text')); + $this->assertFalse($this->component->hasProperty('Caption')); + $this->assertTrue($this->component->hasProperty('content')); + $this->assertFalse($this->component->hasProperty('content', false)); + $this->assertFalse($this->component->hasProperty('Content')); } public function testCanGetProperty() @@ -42,19 +45,26 @@ class ComponentTest extends \yiiunit\TestCase $this->assertTrue($this->component->canGetProperty('Text')); $this->assertTrue($this->component->canGetProperty('text')); $this->assertFalse($this->component->canGetProperty('Caption')); + $this->assertTrue($this->component->canGetProperty('content')); + $this->assertFalse($this->component->canGetProperty('content', false)); + $this->assertFalse($this->component->canGetProperty('Content')); } public function testCanSetProperty() { $this->assertTrue($this->component->canSetProperty('Text')); $this->assertTrue($this->component->canSetProperty('text')); + $this->assertFalse($this->component->canSetProperty('Object')); $this->assertFalse($this->component->canSetProperty('Caption')); + $this->assertTrue($this->component->canSetProperty('content')); + $this->assertFalse($this->component->canSetProperty('content', false)); + $this->assertFalse($this->component->canSetProperty('Content')); } public function testGetProperty() { $this->assertTrue('default' === $this->component->Text); - $this->setExpectedException('yii\base\Exception'); + $this->setExpectedException('yii\base\BadPropertyException'); $value2 = $this->component->Caption; } @@ -62,24 +72,30 @@ class ComponentTest extends \yiiunit\TestCase { $value = 'new value'; $this->component->Text = $value; - $text = $this->component->Text; - $this->assertTrue($value === $this->component->Text); - $this->setExpectedException('yii\base\Exception'); + $this->assertEquals($value, $this->component->Text); + $this->setExpectedException('yii\base\BadPropertyException'); $this->component->NewMember = $value; } public function testIsset() { $this->assertTrue(isset($this->component->Text)); - $this->assertTrue(!empty($this->component->Text)); - - unset($this->component->Text); - $this->assertFalse(isset($this->component->Text)); - $this->assertFalse(!empty($this->component->Text)); + $this->assertFalse(empty($this->component->Text)); $this->component->Text = ''; $this->assertTrue(isset($this->component->Text)); $this->assertTrue(empty($this->component->Text)); + + $this->component->Text = null; + $this->assertFalse(isset($this->component->Text)); + $this->assertTrue(empty($this->component->Text)); + } + + public function testUnset() + { + unset($this->component->Text); + $this->assertFalse(isset($this->component->Text)); + $this->assertTrue(empty($this->component->Text)); } public function testOn() @@ -147,38 +163,24 @@ class ComponentTest extends \yiiunit\TestCase $this->assertFalse($this->component->eventHandled); } - public function testDetachBehavior() + public function testAttachBehavior() { $component = new NewComponent; - $behavior = new NewBehavior; - $component->attachBehavior('a', $behavior); - $this->assertSame($behavior, $component->detachBehavior('a')); - } + $this->assertFalse($component->hasProperty('p')); + $this->assertFalse($component->behaviorCalled); + $this->assertNull($component->getBehavior('a')); - public function testDetachingBehaviors() - { - $component = new NewComponent; - $behavior = new NewBehavior; - $component->attachBehavior('a', $behavior); - $component->detachBehaviors(); - $this->setExpectedException('yii\base\Exception'); - $component->test(); - } - - public function testGetBehavior() - { - $component = new NewComponent; $behavior = new NewBehavior; $component->attachBehavior('a', $behavior); $this->assertSame($behavior, $component->getBehavior('a')); - } + $this->assertTrue($component->hasProperty('p')); + $component->test(); + $this->assertTrue($component->behaviorCalled); - public function testCreate() - { - $component = NewComponent2::newInstance(array('a' => 3), 1, 2); - $this->assertEquals(1, $component->b); - $this->assertEquals(2, $component->c); - $this->assertEquals(3, $component->a); + $this->assertSame($behavior, $component->detachBehavior('a')); + $this->assertFalse($component->hasProperty('p')); + $this->setExpectedException('yii\base\BadMethodException'); + $component->test(); } } @@ -186,9 +188,8 @@ class NewComponent extends \yii\base\Component { private $_object = null; private $_text = 'default'; - public $eventHandled = false; - public $event; - public $behaviorCalled = false; + private $_items = array(); + public $content; public function getText() { @@ -203,12 +204,28 @@ class NewComponent extends \yii\base\Component public function getObject() { if (!$this->_object) { - $this->_object = new NewComponent; + $this->_object = new self; $this->_object->_text = 'object text'; } return $this->_object; } + public function getExecute() + { + return function($param) { + return $param * 2; + }; + } + + public function getItems() + { + return $this->_items; + } + + public $eventHandled = false; + public $event; + public $behaviorCalled = false; + public function myEventHandler($event) { $this->eventHandled = true; @@ -223,6 +240,8 @@ class NewComponent extends \yii\base\Component class NewBehavior extends \yii\base\Behavior { + public $p; + public function test() { $this->owner->behaviorCalled = true; diff --git a/tests/unit/framework/base/DictionaryTest.php b/tests/unit/framework/base/DictionaryTest.php index 2fe1257..fc84dd8 100644 --- a/tests/unit/framework/base/DictionaryTest.php +++ b/tests/unit/framework/base/DictionaryTest.php @@ -11,6 +11,9 @@ class MapItem class DictionaryTest extends \yiiunit\TestCase { + /** + * @var \yii\base\Dictionary + */ protected $dictionary; protected $item1,$item2,$item3; @@ -92,7 +95,7 @@ class DictionaryTest extends \yiiunit\TestCase $this->assertEquals($this->item3, $this->dictionary['key3']); $this->assertEquals($this->item1, $this->dictionary['key4']); - $this->setExpectedException('yii\base\Exception'); + $this->setExpectedException('yii\base\BadParamException'); $this->dictionary->copyFrom($this); } @@ -111,7 +114,7 @@ class DictionaryTest extends \yiiunit\TestCase $this->assertEquals(3,$this->dictionary->getCount()); $this->assertEquals($this->item1,$this->dictionary['key2']); $this->assertEquals($this->item3,$this->dictionary['key3']); - $this->setExpectedException('yii\base\Exception'); + $this->setExpectedException('yii\base\BadParamException'); $this->dictionary->mergeWith($this,false); } diff --git a/tests/unit/framework/base/VectorTest.php b/tests/unit/framework/base/VectorTest.php index 66cbfd3..5943479 100644 --- a/tests/unit/framework/base/VectorTest.php +++ b/tests/unit/framework/base/VectorTest.php @@ -11,6 +11,9 @@ class ListItem class VectorTest extends \yiiunit\TestCase { + /** + * @var Vector + */ protected $vector; protected $item1, $item2, $item3; @@ -62,7 +65,7 @@ class VectorTest extends \yiiunit\TestCase $this->assertEquals(2,$this->vector->indexOf($this->item2)); $this->assertEquals(0,$this->vector->indexOf($this->item3)); $this->assertEquals(1,$this->vector->indexOf($this->item1)); - $this->setExpectedException('yii\base\Exception'); + $this->setExpectedException('yii\base\BadParamException'); $this->vector->insertAt(4,$this->item3); } @@ -84,7 +87,7 @@ class VectorTest extends \yiiunit\TestCase $this->assertEquals(-1,$this->vector->indexOf($this->item2)); $this->assertEquals(1,$this->vector->indexOf($this->item3)); $this->assertEquals(0,$this->vector->indexOf($this->item1)); - $this->setExpectedException('yii\base\Exception'); + $this->setExpectedException('yii\base\BadParamException'); $this->vector->removeAt(2); } @@ -115,7 +118,7 @@ class VectorTest extends \yiiunit\TestCase $array=array($this->item3,$this->item1); $this->vector->copyFrom($array); $this->assertTrue(count($array)==2 && $this->vector[0]===$this->item3 && $this->vector[1]===$this->item1); - $this->setExpectedException('yii\base\Exception'); + $this->setExpectedException('yii\base\BadParamException'); $this->vector->copyFrom($this); } @@ -124,7 +127,7 @@ class VectorTest extends \yiiunit\TestCase $array=array($this->item3,$this->item1); $this->vector->mergeWith($array); $this->assertTrue($this->vector->getCount()==4 && $this->vector[0]===$this->item1 && $this->vector[3]===$this->item1); - $this->setExpectedException('yii\base\Exception'); + $this->setExpectedException('yii\base\BadParamException'); $this->vector->mergeWith($this); } @@ -138,7 +141,7 @@ class VectorTest extends \yiiunit\TestCase { $this->assertTrue($this->vector[0]===$this->item1); $this->assertTrue($this->vector[1]===$this->item2); - $this->setExpectedException('yii\base\Exception'); + $this->setExpectedException('yii\base\BadParamException'); $a=$this->vector[2]; }