PageRenderTime 45ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/framework/base/CComponent.php

http://github.com/yiisoft/yii
PHP | 696 lines | 301 code | 30 blank | 365 comment | 54 complexity | 942aab0ae67056f71fdb4a50f7569ba4 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * This file contains the foundation classes for component-based and event-driven programming.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright 2008-2013 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * CComponent is the base class for all components.
  12. *
  13. * CComponent implements the protocol of defining, using properties and events.
  14. *
  15. * A property is defined by a getter method, and/or a setter method.
  16. * Properties can be accessed in the way like accessing normal object members.
  17. * Reading or writing a property will cause the invocation of the corresponding
  18. * getter or setter method, e.g
  19. * <pre>
  20. * $a=$component->text; // equivalent to $a=$component->getText();
  21. * $component->text='abc'; // equivalent to $component->setText('abc');
  22. * </pre>
  23. * The signatures of getter and setter methods are as follows,
  24. * <pre>
  25. * // getter, defines a readable property 'text'
  26. * public function getText() { ... }
  27. * // setter, defines a writable property 'text' with $value to be set to the property
  28. * public function setText($value) { ... }
  29. * </pre>
  30. *
  31. * An event is defined by the presence of a method whose name starts with 'on'.
  32. * The event name is the method name. When an event is raised, functions
  33. * (called event handlers) attached to the event will be invoked automatically.
  34. *
  35. * An event can be raised by calling {@link raiseEvent} method, upon which
  36. * the attached event handlers will be invoked automatically in the order they
  37. * are attached to the event. Event handlers must have the following signature,
  38. * <pre>
  39. * function eventHandler($event) { ... }
  40. * </pre>
  41. * where $event includes parameters associated with the event.
  42. *
  43. * To attach an event handler to an event, see {@link attachEventHandler}.
  44. * You can also use the following syntax:
  45. * <pre>
  46. * $component->onClick=$callback; // or $component->onClick->add($callback);
  47. * </pre>
  48. * where $callback refers to a valid PHP callback. Below we show some callback examples:
  49. * <pre>
  50. * 'handleOnClick' // handleOnClick() is a global function
  51. * array($object,'handleOnClick') // using $object->handleOnClick()
  52. * array('Page','handleOnClick') // using Page::handleOnClick()
  53. * </pre>
  54. *
  55. * To raise an event, use {@link raiseEvent}. The on-method defining an event is
  56. * commonly written like the following:
  57. * <pre>
  58. * public function onClick($event)
  59. * {
  60. * $this->raiseEvent('onClick',$event);
  61. * }
  62. * </pre>
  63. * where <code>$event</code> is an instance of {@link CEvent} or its child class.
  64. * One can then raise the event by calling the on-method instead of {@link raiseEvent} directly.
  65. *
  66. * Both property names and event names are case-insensitive.
  67. *
  68. * CComponent supports behaviors. A behavior is an
  69. * instance of {@link IBehavior} which is attached to a component. The methods of
  70. * the behavior can be invoked as if they belong to the component. Multiple behaviors
  71. * can be attached to the same component.
  72. *
  73. * To attach a behavior to a component, call {@link attachBehavior}; and to detach the behavior
  74. * from the component, call {@link detachBehavior}.
  75. *
  76. * A behavior can be temporarily enabled or disabled by calling {@link enableBehavior}
  77. * or {@link disableBehavior}, respectively. When disabled, the behavior methods cannot
  78. * be invoked via the component.
  79. *
  80. * Starting from version 1.1.0, a behavior's properties (either its public member variables or
  81. * its properties defined via getters and/or setters) can be accessed through the component it
  82. * is attached to.
  83. *
  84. * @author Qiang Xue <qiang.xue@gmail.com>
  85. * @package system.base
  86. * @since 1.0
  87. */
  88. class CComponent
  89. {
  90. private $_e;
  91. private $_m;
  92. /**
  93. * Returns a property value, an event handler list or a behavior based on its name.
  94. * Do not call this method. This is a PHP magic method that we override
  95. * to allow using the following syntax to read a property or obtain event handlers:
  96. * <pre>
  97. * $value=$component->propertyName;
  98. * $handlers=$component->eventName;
  99. * </pre>
  100. * @param string $name the property name or event name
  101. * @return mixed the property value, event handlers attached to the event, or the named behavior
  102. * @throws CException if the property or event is not defined
  103. * @see __set
  104. */
  105. public function __get($name)
  106. {
  107. $getter='get'.$name;
  108. if(method_exists($this,$getter))
  109. return $this->$getter();
  110. elseif(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
  111. {
  112. // duplicating getEventHandlers() here for performance
  113. $name=strtolower($name);
  114. if(!isset($this->_e[$name]))
  115. $this->_e[$name]=new CList;
  116. return $this->_e[$name];
  117. }
  118. elseif(isset($this->_m[$name]))
  119. return $this->_m[$name];
  120. elseif(is_array($this->_m))
  121. {
  122. foreach($this->_m as $object)
  123. {
  124. if($object->getEnabled() && (property_exists($object,$name) || $object->canGetProperty($name)))
  125. return $object->$name;
  126. }
  127. }
  128. throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',
  129. array('{class}'=>get_class($this), '{property}'=>$name)));
  130. }
  131. /**
  132. * Sets value of a component property.
  133. * Do not call this method. This is a PHP magic method that we override
  134. * to allow using the following syntax to set a property or attach an event handler
  135. * <pre>
  136. * $this->propertyName=$value;
  137. * $this->eventName=$callback;
  138. * </pre>
  139. * @param string $name the property name or the event name
  140. * @param mixed $value the property value or callback
  141. * @return mixed
  142. * @throws CException if the property/event is not defined or the property is read only.
  143. * @see __get
  144. */
  145. public function __set($name,$value)
  146. {
  147. $setter='set'.$name;
  148. if(method_exists($this,$setter))
  149. return $this->$setter($value);
  150. elseif(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
  151. {
  152. // duplicating getEventHandlers() here for performance
  153. $name=strtolower($name);
  154. if(!isset($this->_e[$name]))
  155. $this->_e[$name]=new CList;
  156. return $this->_e[$name]->add($value);
  157. }
  158. elseif(is_array($this->_m))
  159. {
  160. foreach($this->_m as $object)
  161. {
  162. if($object->getEnabled() && (property_exists($object,$name) || $object->canSetProperty($name)))
  163. return $object->$name=$value;
  164. }
  165. }
  166. if(method_exists($this,'get'.$name))
  167. throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.',
  168. array('{class}'=>get_class($this), '{property}'=>$name)));
  169. else
  170. throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',
  171. array('{class}'=>get_class($this), '{property}'=>$name)));
  172. }
  173. /**
  174. * Checks if a property value is null.
  175. * Do not call this method. This is a PHP magic method that we override
  176. * to allow using isset() to detect if a component property is set or not.
  177. * @param string $name the property name or the event name
  178. * @return boolean
  179. */
  180. public function __isset($name)
  181. {
  182. $getter='get'.$name;
  183. if(method_exists($this,$getter))
  184. return $this->$getter()!==null;
  185. elseif(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
  186. {
  187. $name=strtolower($name);
  188. return isset($this->_e[$name]) && $this->_e[$name]->getCount();
  189. }
  190. elseif(is_array($this->_m))
  191. {
  192. if(isset($this->_m[$name]))
  193. return true;
  194. foreach($this->_m as $object)
  195. {
  196. if($object->getEnabled() && (property_exists($object,$name) || $object->canGetProperty($name)))
  197. return $object->$name!==null;
  198. }
  199. }
  200. return false;
  201. }
  202. /**
  203. * Sets a component property to be null.
  204. * Do not call this method. This is a PHP magic method that we override
  205. * to allow using unset() to set a component property to be null.
  206. * @param string $name the property name or the event name
  207. * @throws CException if the property is read only.
  208. * @return mixed
  209. */
  210. public function __unset($name)
  211. {
  212. $setter='set'.$name;
  213. if(method_exists($this,$setter))
  214. $this->$setter(null);
  215. elseif(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
  216. unset($this->_e[strtolower($name)]);
  217. elseif(is_array($this->_m))
  218. {
  219. if(isset($this->_m[$name]))
  220. $this->detachBehavior($name);
  221. else
  222. {
  223. foreach($this->_m as $object)
  224. {
  225. if($object->getEnabled())
  226. {
  227. if(property_exists($object,$name))
  228. return $object->$name=null;
  229. elseif($object->canSetProperty($name))
  230. return $object->$setter(null);
  231. }
  232. }
  233. }
  234. }
  235. elseif(method_exists($this,'get'.$name))
  236. throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.',
  237. array('{class}'=>get_class($this), '{property}'=>$name)));
  238. }
  239. /**
  240. * Calls the named method which is not a class method.
  241. * Do not call this method. This is a PHP magic method that we override
  242. * to implement the behavior feature.
  243. * @param string $name the method name
  244. * @param array $parameters method parameters
  245. * @throws CException if current class and its behaviors do not have a method or closure with the given name
  246. * @return mixed the method return value
  247. */
  248. public function __call($name,$parameters)
  249. {
  250. if($this->_m!==null)
  251. {
  252. foreach($this->_m as $object)
  253. {
  254. if($object->getEnabled() && method_exists($object,$name))
  255. return call_user_func_array(array($object,$name),$parameters);
  256. }
  257. }
  258. if(class_exists('Closure', false) && ($this->canGetProperty($name) || property_exists($this, $name)) && $this->$name instanceof Closure)
  259. return call_user_func_array($this->$name, $parameters);
  260. throw new CException(Yii::t('yii','{class} and its behaviors do not have a method or closure named "{name}".',
  261. array('{class}'=>get_class($this), '{name}'=>$name)));
  262. }
  263. /**
  264. * Returns the named behavior object.
  265. * The name 'asa' stands for 'as a'.
  266. * @param string $behavior the behavior name
  267. * @return IBehavior the behavior object, or null if the behavior does not exist
  268. */
  269. public function asa($behavior)
  270. {
  271. return isset($this->_m[$behavior]) ? $this->_m[$behavior] : null;
  272. }
  273. /**
  274. * Attaches a list of behaviors to the component.
  275. * Each behavior is indexed by its name and should be an instance of
  276. * {@link IBehavior}, a string specifying the behavior class, or an
  277. * array of the following structure:
  278. * <pre>
  279. * array(
  280. * 'class'=>'path.to.BehaviorClass',
  281. * 'property1'=>'value1',
  282. * 'property2'=>'value2',
  283. * )
  284. * </pre>
  285. * @param array $behaviors list of behaviors to be attached to the component
  286. */
  287. public function attachBehaviors($behaviors)
  288. {
  289. foreach($behaviors as $name=>$behavior)
  290. $this->attachBehavior($name,$behavior);
  291. }
  292. /**
  293. * Detaches all behaviors from the component.
  294. */
  295. public function detachBehaviors()
  296. {
  297. if($this->_m!==null)
  298. {
  299. foreach($this->_m as $name=>$behavior)
  300. $this->detachBehavior($name);
  301. $this->_m=null;
  302. }
  303. }
  304. /**
  305. * Attaches a behavior to this component.
  306. * This method will create the behavior object based on the given
  307. * configuration. After that, the behavior object will be initialized
  308. * by calling its {@link IBehavior::attach} method.
  309. * @param string $name the behavior's name. It should uniquely identify this behavior.
  310. * @param mixed $behavior the behavior configuration. This is passed as the first
  311. * parameter to {@link YiiBase::createComponent} to create the behavior object.
  312. * You can also pass an already created behavior instance (the new behavior will replace an already created
  313. * behavior with the same name, if it exists).
  314. * @return IBehavior the behavior object
  315. */
  316. public function attachBehavior($name,$behavior)
  317. {
  318. if(!($behavior instanceof IBehavior))
  319. $behavior=Yii::createComponent($behavior);
  320. $behavior->setEnabled(true);
  321. $behavior->attach($this);
  322. return $this->_m[$name]=$behavior;
  323. }
  324. /**
  325. * Detaches a behavior from the component.
  326. * The behavior's {@link IBehavior::detach} method will be invoked.
  327. * @param string $name the behavior's name. It uniquely identifies the behavior.
  328. * @return IBehavior the detached behavior. Null if the behavior does not exist.
  329. */
  330. public function detachBehavior($name)
  331. {
  332. if(isset($this->_m[$name]))
  333. {
  334. $this->_m[$name]->detach($this);
  335. $behavior=$this->_m[$name];
  336. unset($this->_m[$name]);
  337. return $behavior;
  338. }
  339. }
  340. /**
  341. * Enables all behaviors attached to this component.
  342. */
  343. public function enableBehaviors()
  344. {
  345. if($this->_m!==null)
  346. {
  347. foreach($this->_m as $behavior)
  348. $behavior->setEnabled(true);
  349. }
  350. }
  351. /**
  352. * Disables all behaviors attached to this component.
  353. */
  354. public function disableBehaviors()
  355. {
  356. if($this->_m!==null)
  357. {
  358. foreach($this->_m as $behavior)
  359. $behavior->setEnabled(false);
  360. }
  361. }
  362. /**
  363. * Enables an attached behavior.
  364. * A behavior is only effective when it is enabled.
  365. * A behavior is enabled when first attached.
  366. * @param string $name the behavior's name. It uniquely identifies the behavior.
  367. */
  368. public function enableBehavior($name)
  369. {
  370. if(isset($this->_m[$name]))
  371. $this->_m[$name]->setEnabled(true);
  372. }
  373. /**
  374. * Disables an attached behavior.
  375. * A behavior is only effective when it is enabled.
  376. * @param string $name the behavior's name. It uniquely identifies the behavior.
  377. */
  378. public function disableBehavior($name)
  379. {
  380. if(isset($this->_m[$name]))
  381. $this->_m[$name]->setEnabled(false);
  382. }
  383. /**
  384. * Determines whether a property is defined.
  385. * A property is defined if there is a getter or setter method
  386. * defined in the class. Note, property names are case-insensitive.
  387. * @param string $name the property name
  388. * @return boolean whether the property is defined
  389. * @see canGetProperty
  390. * @see canSetProperty
  391. */
  392. public function hasProperty($name)
  393. {
  394. return method_exists($this,'get'.$name) || method_exists($this,'set'.$name);
  395. }
  396. /**
  397. * Determines whether a property can be read.
  398. * A property can be read if the class has a getter method
  399. * for the property name. Note, property name is case-insensitive.
  400. * @param string $name the property name
  401. * @return boolean whether the property can be read
  402. * @see canSetProperty
  403. */
  404. public function canGetProperty($name)
  405. {
  406. return method_exists($this,'get'.$name);
  407. }
  408. /**
  409. * Determines whether a property can be set.
  410. * A property can be written if the class has a setter method
  411. * for the property name. Note, property name is case-insensitive.
  412. * @param string $name the property name
  413. * @return boolean whether the property can be written
  414. * @see canGetProperty
  415. */
  416. public function canSetProperty($name)
  417. {
  418. return method_exists($this,'set'.$name);
  419. }
  420. /**
  421. * Determines whether an event is defined.
  422. * An event is defined if the class has a method named like 'onXXX'.
  423. * Note, event name is case-insensitive.
  424. * @param string $name the event name
  425. * @return boolean whether an event is defined
  426. */
  427. public function hasEvent($name)
  428. {
  429. return !strncasecmp($name,'on',2) && method_exists($this,$name);
  430. }
  431. /**
  432. * Checks whether the named event has attached handlers.
  433. * @param string $name the event name
  434. * @return boolean whether an event has been attached one or several handlers
  435. */
  436. public function hasEventHandler($name)
  437. {
  438. $name=strtolower($name);
  439. return isset($this->_e[$name]) && $this->_e[$name]->getCount()>0;
  440. }
  441. /**
  442. * Returns the list of attached event handlers for an event.
  443. * @param string $name the event name
  444. * @return CList list of attached event handlers for the event
  445. * @throws CException if the event is not defined
  446. */
  447. public function getEventHandlers($name)
  448. {
  449. if($this->hasEvent($name))
  450. {
  451. $name=strtolower($name);
  452. if(!isset($this->_e[$name]))
  453. $this->_e[$name]=new CList;
  454. return $this->_e[$name];
  455. }
  456. else
  457. throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.',
  458. array('{class}'=>get_class($this), '{event}'=>$name)));
  459. }
  460. /**
  461. * Attaches an event handler to an event.
  462. *
  463. * An event handler must be a valid PHP callback, i.e., a string referring to
  464. * a global function name, or an array containing two elements with
  465. * the first element being an object and the second element a method name
  466. * of the object.
  467. *
  468. * An event handler must be defined with the following signature,
  469. * <pre>
  470. * function handlerName($event) {}
  471. * </pre>
  472. * where $event includes parameters associated with the event.
  473. *
  474. * This is a convenient method of attaching a handler to an event.
  475. * It is equivalent to the following code:
  476. * <pre>
  477. * $component->getEventHandlers($eventName)->add($eventHandler);
  478. * </pre>
  479. *
  480. * Using {@link getEventHandlers}, one can also specify the execution order
  481. * of multiple handlers attaching to the same event. For example:
  482. * <pre>
  483. * $component->getEventHandlers($eventName)->insertAt(0,$eventHandler);
  484. * </pre>
  485. * makes the handler to be invoked first.
  486. *
  487. * @param string $name the event name
  488. * @param callable $handler the event handler
  489. * @throws CException if the event is not defined
  490. * @see detachEventHandler
  491. */
  492. public function attachEventHandler($name,$handler)
  493. {
  494. $this->getEventHandlers($name)->add($handler);
  495. }
  496. /**
  497. * Detaches an existing event handler.
  498. * This method is the opposite of {@link attachEventHandler}.
  499. * @param string $name event name
  500. * @param callable $handler the event handler to be removed
  501. * @return boolean if the detachment process is successful
  502. * @see attachEventHandler
  503. */
  504. public function detachEventHandler($name,$handler)
  505. {
  506. if($this->hasEventHandler($name))
  507. return $this->getEventHandlers($name)->remove($handler)!==false;
  508. else
  509. return false;
  510. }
  511. /**
  512. * Raises an event.
  513. * This method represents the happening of an event. It invokes
  514. * all attached handlers for the event.
  515. * @param string $name the event name
  516. * @param CEvent $event the event parameter
  517. * @throws CException if the event is undefined or an event handler is invalid.
  518. */
  519. public function raiseEvent($name,$event)
  520. {
  521. $name=strtolower($name);
  522. if(isset($this->_e[$name]))
  523. {
  524. foreach($this->_e[$name] as $handler)
  525. {
  526. if(is_string($handler))
  527. call_user_func($handler,$event);
  528. elseif(is_callable($handler,true))
  529. {
  530. if(is_array($handler))
  531. {
  532. // an array: 0 - object, 1 - method name
  533. list($object,$method)=$handler;
  534. if(is_string($object)) // static method call
  535. call_user_func($handler,$event);
  536. elseif(method_exists($object,$method))
  537. $object->$method($event);
  538. else
  539. throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".',
  540. array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>$handler[1])));
  541. }
  542. else // PHP 5.3: anonymous function
  543. call_user_func($handler,$event);
  544. }
  545. else
  546. throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".',
  547. array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>gettype($handler))));
  548. // stop further handling if param.handled is set true
  549. if(($event instanceof CEvent) && $event->handled)
  550. return;
  551. }
  552. }
  553. elseif(YII_DEBUG && !$this->hasEvent($name))
  554. throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.',
  555. array('{class}'=>get_class($this), '{event}'=>$name)));
  556. }
  557. /**
  558. * Evaluates a PHP expression or callback under the context of this component.
  559. *
  560. * Valid PHP callback can be class method name in the form of
  561. * array(ClassName/Object, MethodName), or anonymous function (only available in PHP 5.3.0 or above).
  562. *
  563. * If a PHP callback is used, the corresponding function/method signature should be
  564. * <pre>
  565. * function foo($param1, $param2, ..., $component) { ... }
  566. * </pre>
  567. * where the array elements in the second parameter to this method will be passed
  568. * to the callback as $param1, $param2, ...; and the last parameter will be the component itself.
  569. *
  570. * If a PHP expression is used, the second parameter will be "extracted" into PHP variables
  571. * that can be directly accessed in the expression. See {@link http://us.php.net/manual/en/function.extract.php PHP extract}
  572. * for more details. In the expression, the component object can be accessed using $this.
  573. *
  574. * A PHP expression can be any PHP code that has a value. To learn more about what an expression is,
  575. * please refer to the {@link http://www.php.net/manual/en/language.expressions.php php manual}.
  576. *
  577. * @param mixed $_expression_ a PHP expression or PHP callback to be evaluated.
  578. * @param array $_data_ additional parameters to be passed to the above expression/callback.
  579. * @return mixed the expression result
  580. * @since 1.1.0
  581. */
  582. public function evaluateExpression($_expression_,$_data_=array())
  583. {
  584. if(is_string($_expression_))
  585. {
  586. extract($_data_);
  587. try
  588. {
  589. return eval('return ' . $_expression_ . ';');
  590. }
  591. catch (ParseError $e)
  592. {
  593. return false;
  594. }
  595. }
  596. else
  597. {
  598. $_data_[]=$this;
  599. return call_user_func_array($_expression_, array_values($_data_));
  600. }
  601. }
  602. }
  603. /**
  604. * CEvent is the base class for all event classes.
  605. *
  606. * It encapsulates the parameters associated with an event.
  607. * The {@link sender} property describes who raises the event.
  608. * And the {@link handled} property indicates if the event is handled.
  609. * If an event handler sets {@link handled} to true, those handlers
  610. * that are not invoked yet will not be invoked anymore.
  611. *
  612. * @author Qiang Xue <qiang.xue@gmail.com>
  613. * @package system.base
  614. * @since 1.0
  615. */
  616. class CEvent extends CComponent
  617. {
  618. /**
  619. * @var object the sender of this event
  620. */
  621. public $sender;
  622. /**
  623. * @var boolean whether the event is handled. Defaults to false.
  624. * When a handler sets this true, the rest of the uninvoked event handlers will not be invoked anymore.
  625. */
  626. public $handled=false;
  627. /**
  628. * @var mixed additional event parameters.
  629. * @since 1.1.7
  630. */
  631. public $params;
  632. /**
  633. * Constructor.
  634. * @param mixed $sender sender of the event
  635. * @param mixed $params additional parameters for the event
  636. */
  637. public function __construct($sender=null,$params=null)
  638. {
  639. $this->sender=$sender;
  640. $this->params=$params;
  641. }
  642. }
  643. /**
  644. * CEnumerable is the base class for all enumerable types.
  645. *
  646. * To define an enumerable type, extend CEnumberable and define string constants.
  647. * Each constant represents an enumerable value.
  648. * The constant name must be the same as the constant value.
  649. * For example,
  650. * <pre>
  651. * class TextAlign extends CEnumerable
  652. * {
  653. * const Left='Left';
  654. * const Right='Right';
  655. * }
  656. * </pre>
  657. * Then, one can use the enumerable values such as TextAlign::Left and
  658. * TextAlign::Right.
  659. *
  660. * @author Qiang Xue <qiang.xue@gmail.com>
  661. * @package system.base
  662. * @since 1.0
  663. */
  664. class CEnumerable
  665. {
  666. }