PageRenderTime 53ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/framework/yii/base/Component.php

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