PageRenderTime 43ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/framework/base/Component.php

https://github.com/rlerdorf/yii2
PHP | 552 lines | 257 code | 27 blank | 268 comment | 45 complexity | e54c79600fee5534ca8ea0b4e37706d2 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\base;
  8. use Yii;
  9. /**
  10. * @include @yii/base/Component.md
  11. * @author Qiang Xue <qiang.xue@gmail.com>
  12. * @since 2.0
  13. */
  14. class Component extends Object
  15. {
  16. /**
  17. * @var array the attached event handlers (event name => handlers)
  18. */
  19. private $_events;
  20. /**
  21. * @var Behavior[] the attached behaviors (behavior name => behavior)
  22. */
  23. private $_behaviors;
  24. /**
  25. * Returns the value of a component property.
  26. * This method will check in the following order and act accordingly:
  27. *
  28. * - a property defined by a getter: return the getter result
  29. * - a property of a behavior: return the behavior property value
  30. *
  31. * Do not call this method directly as it is a PHP magic method that
  32. * will be implicitly called when executing `$value = $component->property;`.
  33. * @param string $name the property name
  34. * @return mixed the property value, event handlers attached to the event,
  35. * the behavior, or the value of a behavior's property
  36. * @throws UnknownPropertyException if the property is not defined
  37. * @see __set
  38. */
  39. public function __get($name)
  40. {
  41. $getter = 'get' . $name;
  42. if (method_exists($this, $getter)) {
  43. // read property, e.g. getName()
  44. return $this->$getter();
  45. } else {
  46. // behavior property
  47. $this->ensureBehaviors();
  48. foreach ($this->_behaviors as $behavior) {
  49. if ($behavior->canGetProperty($name)) {
  50. return $behavior->$name;
  51. }
  52. }
  53. }
  54. throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
  55. }
  56. /**
  57. * Sets the value of a component property.
  58. * This method will check in the following order and act accordingly:
  59. *
  60. * - a property defined by a setter: set the property value
  61. * - an event in the format of "on xyz": attach the handler to the event "xyz"
  62. * - a behavior in the format of "as xyz": attach the behavior named as "xyz"
  63. * - a property of a behavior: set the behavior property value
  64. *
  65. * Do not call this method directly as it is a PHP magic method that
  66. * will be implicitly called when executing `$component->property = $value;`.
  67. * @param string $name the property name or the event name
  68. * @param mixed $value the property value
  69. * @throws UnknownPropertyException if the property is not defined
  70. * @throws InvalidCallException if the property is read-only.
  71. * @see __get
  72. */
  73. public function __set($name, $value)
  74. {
  75. $setter = 'set' . $name;
  76. if (method_exists($this, $setter)) {
  77. // set property
  78. $this->$setter($value);
  79. return;
  80. } elseif (strncmp($name, 'on ', 3) === 0) {
  81. // on event: attach event handler
  82. $this->on(trim(substr($name, 3)), $value);
  83. return;
  84. } elseif (strncmp($name, 'as ', 3) === 0) {
  85. // as behavior: attach behavior
  86. $name = trim(substr($name, 3));
  87. $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));
  88. } else {
  89. // behavior property
  90. $this->ensureBehaviors();
  91. foreach ($this->_behaviors as $behavior) {
  92. if ($behavior->canSetProperty($name)) {
  93. $behavior->$name = $value;
  94. return;
  95. }
  96. }
  97. }
  98. if (method_exists($this, 'get' . $name)) {
  99. throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
  100. } else {
  101. throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
  102. }
  103. }
  104. /**
  105. * Checks if a property value is null.
  106. * This method will check in the following order and act accordingly:
  107. *
  108. * - a property defined by a setter: return whether the property value is null
  109. * - a property of a behavior: return whether the property value is null
  110. *
  111. * Do not call this method directly as it is a PHP magic method that
  112. * will be implicitly called when executing `isset($component->property)`.
  113. * @param string $name the property name or the event name
  114. * @return boolean whether the named property is null
  115. */
  116. public function __isset($name)
  117. {
  118. $getter = 'get' . $name;
  119. if (method_exists($this, $getter)) {
  120. return $this->$getter() !== null;
  121. } else {
  122. // behavior property
  123. $this->ensureBehaviors();
  124. foreach ($this->_behaviors as $behavior) {
  125. if ($behavior->canGetProperty($name)) {
  126. return $behavior->$name !== null;
  127. }
  128. }
  129. }
  130. return false;
  131. }
  132. /**
  133. * Sets a component property to be null.
  134. * This method will check in the following order and act accordingly:
  135. *
  136. * - a property defined by a setter: set the property value to be null
  137. * - a property of a behavior: set the property value to be null
  138. *
  139. * Do not call this method directly as it is a PHP magic method that
  140. * will be implicitly called when executing `unset($component->property)`.
  141. * @param string $name the property name
  142. * @throws InvalidCallException if the property is read only.
  143. */
  144. public function __unset($name)
  145. {
  146. $setter = 'set' . $name;
  147. if (method_exists($this, $setter)) {
  148. $this->$setter(null);
  149. return;
  150. } else {
  151. // behavior property
  152. $this->ensureBehaviors();
  153. foreach ($this->_behaviors as $behavior) {
  154. if ($behavior->canSetProperty($name)) {
  155. $behavior->$name = null;
  156. return;
  157. }
  158. }
  159. }
  160. if (method_exists($this, 'get' . $name)) {
  161. throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '.' . $name);
  162. }
  163. }
  164. /**
  165. * Calls the named method which is not a class method.
  166. * If the name refers to a component property whose value is
  167. * an anonymous function, the method will execute the function.
  168. * Otherwise, it will check if any attached behavior has
  169. * the named method and will execute it if available.
  170. *
  171. * Do not call this method directly as it is a PHP magic method that
  172. * will be implicitly called when an unknown method is being invoked.
  173. * @param string $name the method name
  174. * @param array $params method parameters
  175. * @return mixed the method return value
  176. * @throws UnknownMethodException when calling unknown method
  177. */
  178. public function __call($name, $params)
  179. {
  180. $getter = 'get' . $name;
  181. if (method_exists($this, $getter)) {
  182. $func = $this->$getter();
  183. if ($func instanceof \Closure) {
  184. return call_user_func_array($func, $params);
  185. }
  186. }
  187. $this->ensureBehaviors();
  188. foreach ($this->_behaviors as $object) {
  189. if (method_exists($object, $name)) {
  190. return call_user_func_array(array($object, $name), $params);
  191. }
  192. }
  193. throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
  194. }
  195. /**
  196. * This method is called after the object is created by cloning an existing one.
  197. * It removes all behaviors because they are attached to the old object.
  198. */
  199. public function __clone()
  200. {
  201. $this->_events = null;
  202. $this->_behaviors = null;
  203. }
  204. /**
  205. * Returns a value indicating whether a property is defined for this component.
  206. * A property is defined if:
  207. *
  208. * - the class has a getter or setter method associated with the specified name
  209. * (in this case, property name is case-insensitive);
  210. * - the class has a member variable with the specified name (when `$checkVar` is true);
  211. * - an attached behavior has a property of the given name (when `$checkBehavior` is true).
  212. *
  213. * @param string $name the property name
  214. * @param boolean $checkVar whether to treat member variables as properties
  215. * @param boolean $checkBehavior whether to treat behaviors' properties as properties of this component
  216. * @return boolean whether the property is defined
  217. * @see canGetProperty
  218. * @see canSetProperty
  219. */
  220. public function hasProperty($name, $checkVar = true, $checkBehavior = true)
  221. {
  222. return $this->canGetProperty($name, $checkVar, $checkBehavior) || $this->canSetProperty($name, $checkVar, $checkBehavior);
  223. }
  224. /**
  225. * Returns a value indicating whether a property can be read.
  226. * A property can be read if:
  227. *
  228. * - the class has a getter method associated with the specified name
  229. * (in this case, property name is case-insensitive);
  230. * - the class has a member variable with the specified name (when `$checkVar` is true);
  231. * - an attached behavior has a readable property of the given name (when `$checkBehavior` is true).
  232. *
  233. * @param string $name the property name
  234. * @param boolean $checkVar whether to treat member variables as properties
  235. * @param boolean $checkBehavior whether to treat behaviors' properties as properties of this component
  236. * @return boolean whether the property can be read
  237. * @see canSetProperty
  238. */
  239. public function canGetProperty($name, $checkVar = true, $checkBehavior = true)
  240. {
  241. if (method_exists($this, 'get' . $name) || $checkVar && property_exists($this, $name)) {
  242. return true;
  243. } else {
  244. $this->ensureBehaviors();
  245. foreach ($this->_behaviors as $behavior) {
  246. if ($behavior->canGetProperty($name, $checkVar)) {
  247. return true;
  248. }
  249. }
  250. return false;
  251. }
  252. }
  253. /**
  254. * Returns a value indicating whether a property can be set.
  255. * A property can be written if:
  256. *
  257. * - the class has a setter method associated with the specified name
  258. * (in this case, property name is case-insensitive);
  259. * - the class has a member variable with the specified name (when `$checkVar` is true);
  260. * - an attached behavior has a writable property of the given name (when `$checkBehavior` is true).
  261. *
  262. * @param string $name the property name
  263. * @param boolean $checkVar whether to treat member variables as properties
  264. * @param boolean $checkBehavior whether to treat behaviors' properties as properties of this component
  265. * @return boolean whether the property can be written
  266. * @see canGetProperty
  267. */
  268. public function canSetProperty($name, $checkVar = true, $checkBehavior = true)
  269. {
  270. if (method_exists($this, 'set' . $name) || $checkVar && property_exists($this, $name)) {
  271. return true;
  272. } else {
  273. $this->ensureBehaviors();
  274. foreach ($this->_behaviors as $behavior) {
  275. if ($behavior->canSetProperty($name, $checkVar)) {
  276. return true;
  277. }
  278. }
  279. return false;
  280. }
  281. }
  282. /**
  283. * Returns a list of behaviors that this component should behave as.
  284. *
  285. * Child classes may override this method to specify the behaviors they want to behave as.
  286. *
  287. * The return value of this method should be an array of behavior objects or configurations
  288. * indexed by behavior names. A behavior configuration can be either a string specifying
  289. * the behavior class or an array of the following structure:
  290. *
  291. * ~~~
  292. * 'behaviorName' => array(
  293. * 'class' => 'BehaviorClass',
  294. * 'property1' => 'value1',
  295. * 'property2' => 'value2',
  296. * )
  297. * ~~~
  298. *
  299. * Note that a behavior class must extend from [[Behavior]]. Behavior names can be strings
  300. * or integers. If the former, they uniquely identify the behaviors. If the latter, the corresponding
  301. * behaviors are anonymous and their properties and methods will NOT be made available via the component
  302. * (however, the behaviors can still respond to the component's events).
  303. *
  304. * Behaviors declared in this method will be attached to the component automatically (on demand).
  305. *
  306. * @return array the behavior configurations.
  307. */
  308. public function behaviors()
  309. {
  310. return array();
  311. }
  312. /**
  313. * Returns a value indicating whether there is any handler attached to the named event.
  314. * @param string $name the event name
  315. * @return boolean whether there is any handler attached to the event.
  316. */
  317. public function hasEventHandlers($name)
  318. {
  319. $this->ensureBehaviors();
  320. return !empty($this->_events[$name]);
  321. }
  322. /**
  323. * Attaches an event handler to an event.
  324. *
  325. * An event handler must be a valid PHP callback. The followings are
  326. * some examples:
  327. *
  328. * ~~~
  329. * function ($event) { ... } // anonymous function
  330. * array($object, 'handleClick') // $object->handleClick()
  331. * array('Page', 'handleClick') // Page::handleClick()
  332. * 'handleClick' // global function handleClick()
  333. * ~~~
  334. *
  335. * An event handler must be defined with the following signature,
  336. *
  337. * ~~~
  338. * function ($event)
  339. * ~~~
  340. *
  341. * where `$event` is an [[Event]] object which includes parameters associated with the event.
  342. *
  343. * @param string $name the event name
  344. * @param callback $handler the event handler
  345. * @param mixed $data the data to be passed to the event handler when the event is triggered.
  346. * When the event handler is invoked, this data can be accessed via [[Event::data]].
  347. * @see off()
  348. */
  349. public function on($name, $handler, $data = null)
  350. {
  351. $this->ensureBehaviors();
  352. $this->_events[$name][] = array($handler, $data);
  353. }
  354. /**
  355. * Detaches an existing event handler from this component.
  356. * This method is the opposite of [[on()]].
  357. * @param string $name event name
  358. * @param callback $handler the event handler to be removed.
  359. * If it is null, all handlers attached to the named event will be removed.
  360. * @return boolean if a handler is found and detached
  361. * @see on()
  362. */
  363. public function off($name, $handler = null)
  364. {
  365. $this->ensureBehaviors();
  366. if (isset($this->_events[$name])) {
  367. if ($handler === null) {
  368. $this->_events[$name] = array();
  369. } else {
  370. $removed = false;
  371. foreach ($this->_events[$name] as $i => $event) {
  372. if ($event[0] === $handler) {
  373. unset($this->_events[$name][$i]);
  374. $removed = true;
  375. }
  376. }
  377. if ($removed) {
  378. $this->_events[$name] = array_values($this->_events[$name]);
  379. }
  380. return $removed;
  381. }
  382. }
  383. return false;
  384. }
  385. /**
  386. * Triggers an event.
  387. * This method represents the happening of an event. It invokes
  388. * all attached handlers for the event.
  389. * @param string $name the event name
  390. * @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
  391. */
  392. public function trigger($name, $event = null)
  393. {
  394. $this->ensureBehaviors();
  395. if (!empty($this->_events[$name])) {
  396. if ($event === null) {
  397. $event = new Event;
  398. }
  399. if ($event->sender === null) {
  400. $event->sender = $this;
  401. }
  402. $event->handled = false;
  403. $event->name = $name;
  404. foreach ($this->_events[$name] as $handler) {
  405. $event->data = $handler[1];
  406. call_user_func($handler[0], $event);
  407. // stop further handling if the event is handled
  408. if ($event instanceof Event && $event->handled) {
  409. return;
  410. }
  411. }
  412. }
  413. }
  414. /**
  415. * Returns the named behavior object.
  416. * @param string $name the behavior name
  417. * @return Behavior the behavior object, or null if the behavior does not exist
  418. */
  419. public function getBehavior($name)
  420. {
  421. $this->ensureBehaviors();
  422. return isset($this->_behaviors[$name]) ? $this->_behaviors[$name] : null;
  423. }
  424. /**
  425. * Returns all behaviors attached to this component.
  426. * @return Behavior[] list of behaviors attached to this component
  427. */
  428. public function getBehaviors()
  429. {
  430. $this->ensureBehaviors();
  431. return $this->_behaviors;
  432. }
  433. /**
  434. * Attaches a behavior to this component.
  435. * This method will create the behavior object based on the given
  436. * configuration. After that, the behavior object will be attached to
  437. * this component by calling the [[Behavior::attach()]] method.
  438. * @param string $name the name of the behavior.
  439. * @param string|array|Behavior $behavior the behavior configuration. This can be one of the following:
  440. *
  441. * - a [[Behavior]] object
  442. * - a string specifying the behavior class
  443. * - an object configuration array that will be passed to [[Yii::createObject()]] to create the behavior object.
  444. *
  445. * @return Behavior the behavior object
  446. * @see detachBehavior
  447. */
  448. public function attachBehavior($name, $behavior)
  449. {
  450. $this->ensureBehaviors();
  451. return $this->attachBehaviorInternal($name, $behavior);
  452. }
  453. /**
  454. * Attaches a list of behaviors to the component.
  455. * Each behavior is indexed by its name and should be a [[Behavior]] object,
  456. * a string specifying the behavior class, or an configuration array for creating the behavior.
  457. * @param array $behaviors list of behaviors to be attached to the component
  458. * @see attachBehavior
  459. */
  460. public function attachBehaviors($behaviors)
  461. {
  462. $this->ensureBehaviors();
  463. foreach ($behaviors as $name => $behavior) {
  464. $this->attachBehaviorInternal($name, $behavior);
  465. }
  466. }
  467. /**
  468. * Detaches a behavior from the component.
  469. * The behavior's [[Behavior::detach()]] method will be invoked.
  470. * @param string $name the behavior's name.
  471. * @return Behavior the detached behavior. Null if the behavior does not exist.
  472. */
  473. public function detachBehavior($name)
  474. {
  475. if (isset($this->_behaviors[$name])) {
  476. $behavior = $this->_behaviors[$name];
  477. unset($this->_behaviors[$name]);
  478. $behavior->detach();
  479. return $behavior;
  480. } else {
  481. return null;
  482. }
  483. }
  484. /**
  485. * Detaches all behaviors from the component.
  486. */
  487. public function detachBehaviors()
  488. {
  489. if ($this->_behaviors !== null) {
  490. foreach ($this->_behaviors as $name => $behavior) {
  491. $this->detachBehavior($name);
  492. }
  493. }
  494. $this->_behaviors = array();
  495. }
  496. /**
  497. * Makes sure that the behaviors declared in [[behaviors()]] are attached to this component.
  498. */
  499. public function ensureBehaviors()
  500. {
  501. if ($this->_behaviors === null) {
  502. $this->_behaviors = array();
  503. foreach ($this->behaviors() as $name => $behavior) {
  504. $this->attachBehaviorInternal($name, $behavior);
  505. }
  506. }
  507. }
  508. /**
  509. * Attaches a behavior to this component.
  510. * @param string $name the name of the behavior.
  511. * @param string|array|Behavior $behavior the behavior to be attached
  512. * @return Behavior the attached behavior.
  513. */
  514. private function attachBehaviorInternal($name, $behavior)
  515. {
  516. if (!($behavior instanceof Behavior)) {
  517. $behavior = Yii::createObject($behavior);
  518. }
  519. if (isset($this->_behaviors[$name])) {
  520. $this->_behaviors[$name]->detach();
  521. }
  522. $behavior->attach($this);
  523. return $this->_behaviors[$name] = $behavior;
  524. }
  525. }