/protected/components/ezcomponents/SignalSlot/src/signal_collection.php

https://github.com/kamarulismail/kamarul-playground · PHP · 494 lines · 288 code · 31 blank · 175 comment · 61 complexity · c95511beb47d07a6ea0147002abff933 MD5 · raw file

  1. <?php
  2. /**
  3. * @copyright Copyright (C) 2005-2008 eZ systems as. All rights reserved.
  4. * @license http://ez.no/licenses/new_bsd New BSD License
  5. * @version 1.1.1
  6. * @filesource
  7. * @package SignalSlot
  8. */
  9. /**
  10. * ezcSignalCollection implements a mechanism for inter and intra object communication.
  11. *
  12. * See the tutorial for extensive examples on how to use this class.
  13. *
  14. * @property bool $signalsBlocked If set to true emits will not cause any slots to be called.
  15. *
  16. * @property-read string $identifier The identifier of this signal collection.
  17. * Usually the class name of the object containing the collection.
  18. *
  19. * @version 1.1.1
  20. * @mainclass
  21. * @package SignalSlot
  22. */
  23. class ezcSignalCollection
  24. {
  25. /**
  26. * Holds the properties of this class.
  27. *
  28. * @var array(string=>mixed)
  29. */
  30. private $properties = array();
  31. /**
  32. * Holds the connections for this object with the default priority.
  33. *
  34. * @var array(string=>array(callback))
  35. */
  36. private $defaultConnections = array();
  37. /**
  38. * Holds the priority connections for this object.
  39. *
  40. * @var array(string=>array(int=>array(callback)))
  41. */
  42. private $priorityConnections = array();
  43. /**
  44. * If set this object will be used to fetch static connections instead of ezcSignalStaticConnections.
  45. *
  46. * @var ezcSignalStaticConnectionsBase
  47. */
  48. private static $staticConnectionsHolder = null;
  49. /**
  50. * Holds the options for this signal collection
  51. *
  52. * @var ezcSignalCollectionOptions
  53. */
  54. private $options;
  55. /**
  56. * Constructs a new signal collection with the identifier $identifier.
  57. *
  58. * The identifier can be used to connect to signals statically using
  59. * ezcSignalStaticConnections.
  60. *
  61. * Through the associative array options you can specify the options for this class in the
  62. * format array( 'optionName' => value ). See the documentation of ezcSignalCollectionOptions
  63. * for information on the available options.
  64. *
  65. * @param string $identifier
  66. * @param array $options
  67. */
  68. public function __construct( $identifier = "default", array $options = array() )
  69. {
  70. $this->options = new ezcSignalCollectionOptions( $options );
  71. $this->properties['identifier'] = $identifier;
  72. $this->signalsBlocked = false;
  73. }
  74. /**
  75. * If set, $holder will be used to fetch static connections instead of ezcSignalStaticConnections.
  76. *
  77. * @param ezcSignalStaticConnectionsBase $holder
  78. */
  79. public static function setStaticConnectionsHolder( ezcSignalStaticConnectionsBase $holder )
  80. {
  81. self::$staticConnectionsHolder = $holder;
  82. }
  83. /**
  84. * Returns the current provider of static connections or null if there is none.
  85. *
  86. * @return ezcSignalStaticConnectionsBase
  87. */
  88. public static function getStaticConnectionsHolder()
  89. {
  90. return self::$staticConnectionsHolder;
  91. }
  92. /**
  93. * Sets the property $name to $value.
  94. *
  95. * @throws ezcBasePropertyNotFoundException if the property does not exist.
  96. * @param string $name
  97. * @param mixed $value
  98. * @return void
  99. */
  100. public function __set( $name, $value )
  101. {
  102. switch ( $name )
  103. {
  104. case 'signalsBlocked':
  105. $this->properties[$name] = $value;
  106. break;
  107. case 'identifier':
  108. throw new ezcBasePropertyPermissionException( $name, ezcBasePropertyPermissionException::READ );
  109. break;
  110. case 'options':
  111. if ( !( $value instanceof ezcSignalCollectionOptions ) )
  112. {
  113. throw new ezcBaseValueException( 'options', $value, 'instanceof ezcSignalCollectionOptions' );
  114. }
  115. $this->options = $value;
  116. break;
  117. default:
  118. throw new ezcBasePropertyNotFoundException( $name );
  119. break;
  120. }
  121. }
  122. /**
  123. * Returns the property $name.
  124. *
  125. * @throws ezcBasePropertyNotFoundException if the property does not exist.
  126. * @param string $name
  127. * @return mixed
  128. */
  129. public function __get( $name )
  130. {
  131. switch ( $name )
  132. {
  133. case 'signalsBlocked':
  134. case 'identifier':
  135. return $this->properties[$name];
  136. break;
  137. case 'options':
  138. return $this->options;
  139. break;
  140. default:
  141. throw new ezcBasePropertyNotFoundException( $name );
  142. break;
  143. }
  144. }
  145. /**
  146. * Sets the options of this class.
  147. *
  148. * @param ezcSignalCollectionOptions|array(string=>value) $options The options to set
  149. * either as an associative array in the form array(optionName=>value) or a
  150. * ezcSignalCollectionOptions object.
  151. *
  152. * @throws ezcBaseSettingNotFoundException
  153. * If you tried to set a non-existent option value.
  154. * @throws ezcBaseSettingValueException
  155. * If the value is not valid for the desired option.
  156. * @throws ezcBaseValueException
  157. * If you submit neither an array nor an instance of
  158. * ezcSignalCollectionOptions.
  159. */
  160. public function setOptions( $options )
  161. {
  162. if ( $options instanceof ezcSignalCollectionOptions )
  163. {
  164. $this->options = $options;
  165. }
  166. else if ( is_array( $options ) )
  167. {
  168. $this->options = new ezcSignalCollectionOptions( $options );
  169. }
  170. else
  171. {
  172. throw new ezcBaseValueException( "options", $options, 'array or instance of ezcSignalCollectionOptions' );
  173. }
  174. }
  175. /**
  176. * Returns the options for this class.
  177. *
  178. * @return ezcSignalCollectionOptions
  179. */
  180. public function getOptions()
  181. {
  182. return $this->options;
  183. }
  184. /**
  185. * Returns true if any slots have been connected to the signal $signal.
  186. * False is returned if no slots have been connected to the $signal.
  187. *
  188. * Note: Emitting the signal $signal may still not call any slots if
  189. * the property signalsBlocked has been set.
  190. *
  191. * @throws ezcSignalSlotException if the signals options has been set and $signal is not in the list of signals.
  192. * @param string $signal
  193. * @return bool
  194. */
  195. public function isConnected( $signal )
  196. {
  197. // if the the signals option is set we must check if the signal exists
  198. if ( $this->options->signals != null && !in_array( $signal, $this->options->signals ) )
  199. {
  200. throw new ezcSignalSlotException( "No such signal {$signal}" );
  201. }
  202. // static connections
  203. if ( self::$staticConnectionsHolder == null ) // custom static connections class
  204. {
  205. if ( count( ezcSignalStaticConnections::getInstance()->getConnections( $this->identifier, $signal ) ) > 0 )
  206. {
  207. return true;
  208. }
  209. }
  210. else if ( count( self::$staticConnectionsHolder->getConnections( $this->identifier, $signal ) ) > 0 )
  211. {
  212. return true;
  213. }
  214. // default connections
  215. if ( isset( $this->defaultConnections[$signal] ) && count( $this->defaultConnections[$signal] ) > 0 )
  216. {
  217. return true;
  218. }
  219. // priority connections
  220. if ( isset( $this->priorityConnections[$signal] ) && count( $this->priorityConnections[$signal] ) > 0 )
  221. {
  222. return true;
  223. }
  224. return false;
  225. }
  226. /**
  227. * Emits the signal with the name $signal
  228. *
  229. * Any additional parameters are sent as parameters to the slot.
  230. *
  231. * @throws ezcSignalSlotException if the signals options has been set and $signal is not in the list of signals.
  232. * @param string $signal
  233. * @param ... $signal_parameters
  234. * @return void
  235. */
  236. public function emit( $signal )
  237. {
  238. // if the the signals option is set we must check if the signal exists
  239. if ( $this->options->signals != null && !in_array( $signal, $this->options->signals ) )
  240. {
  241. throw new ezcSignalSlotException( "No such signal {$signal}" );
  242. }
  243. if ( $this->signalsBlocked )
  244. {
  245. return;
  246. }
  247. // prepare the parameters
  248. $parameters = array_slice( func_get_args(), 1 );
  249. // check if there are any static connections
  250. $priStaticConnections = array();
  251. if ( self::$staticConnectionsHolder == null )
  252. {
  253. $priStaticConnections = ezcSignalStaticConnections::getInstance()->getConnections( $this->identifier, $signal );
  254. }
  255. else
  256. {
  257. $priStaticConnections = self::$staticConnectionsHolder->getConnections( $this->identifier, $signal );
  258. }
  259. $hasPriStaticConnections = false;
  260. if ( count( $priStaticConnections ) > ( isset( $priStaticConnections[1000] ) ? 1 : 0) )
  261. {
  262. $hasPriStaticConnections = true;
  263. }
  264. // fast algorithm if there are no prioritized slots
  265. if ( isset( $this->priorityConnections[$signal] ) === false && !$hasPriStaticConnections )
  266. {
  267. if ( isset( $this->defaultConnections[$signal] ) )
  268. {
  269. foreach ( $this->defaultConnections[$signal] as $callback )
  270. {
  271. call_user_func_array( $callback, $parameters );
  272. }
  273. }
  274. if ( isset( $priStaticConnections[1000] ) )
  275. {
  276. foreach ( $priStaticConnections[1000] as $callback )
  277. {
  278. call_user_func_array( $callback, $parameters );
  279. }
  280. }
  281. }
  282. else // default algorithm
  283. {
  284. // order slots
  285. $defaultKeys = array();
  286. if ( isset( $this->priorityConnections[$signal] ) )
  287. {
  288. $defaultKeys = array_keys( $this->priorityConnections[$signal] );
  289. }
  290. $staticKeys = array_keys( $priStaticConnections );
  291. $allKeys = array_unique( array_merge( $defaultKeys, $staticKeys, array( 1000 ) /*default*/ ) );
  292. sort( $allKeys, SORT_NUMERIC );
  293. foreach ( $allKeys as $key ) // call all slots in the correct order
  294. {
  295. if ( $key == 1000 && isset( $this->defaultConnections[$signal] ) )
  296. {
  297. foreach ( $this->defaultConnections[$signal] as $callback )
  298. {
  299. call_user_func_array( $callback, $parameters );
  300. }
  301. }
  302. if ( isset( $this->priorityConnections[$signal][$key] ) )
  303. {
  304. foreach ( $this->priorityConnections[$signal][$key] as $callback )
  305. {
  306. call_user_func_array( $callback, $parameters );
  307. }
  308. }
  309. if ( isset( $priStaticConnections[$key] ) )
  310. {
  311. foreach ( $priStaticConnections[$key] as $callback )
  312. {
  313. call_user_func_array( $callback, $parameters );
  314. }
  315. }
  316. }
  317. }
  318. }
  319. /**
  320. * Connects the signal $signal to the slot $slot.
  321. *
  322. * To control the order in which slots are called you can set a priority
  323. * from 1 - 65 536. The lower the number the higher the priority. The default
  324. * priority is 1000.
  325. * Slots with the same priority may be called with in any order.
  326. *
  327. * A slot will be called once for every time it is connected. It is possible
  328. * to connect a slot more than once.
  329. *
  330. * See the PHP documentation for examples on the callback type.
  331. * http://php.net/callback.
  332. *
  333. * We recommend avoiding excessive usage of the $priority parameter
  334. * since it makes it much harder to track how your program works.
  335. *
  336. * @throws ezcSignalSlotException if the signals options has been set and $signal is not in the list of signals.
  337. * @param string $signal
  338. * @param callback $slot
  339. * @param int $priority
  340. * @return void
  341. */
  342. public function connect( $signal, $slot, $priority = 1000 )
  343. {
  344. // if the the signals option is set we must check if the signal exists
  345. if ( $this->options->signals != null && !in_array( $signal, $this->options->signals ) )
  346. {
  347. throw new ezcSignalSlotException( "No such signal {$signal}" );
  348. }
  349. if ( $priority === 1000 ) // default
  350. {
  351. $this->defaultConnections[$signal][] = $slot;
  352. }
  353. else
  354. {
  355. $this->priorityConnections[$signal][$priority][] = $slot;
  356. sort( $this->priorityConnections[$signal][$priority], SORT_NUMERIC );
  357. }
  358. }
  359. /**
  360. * Disconnects the $slot from the $signal.
  361. *
  362. * If the priority is given it will try to disconnect a slot with that priority.
  363. * If no such slot is found no slot will be disconnected.
  364. *
  365. * If no priority is given it will disconnect the matching slot with the lowest priority.
  366. *
  367. * @throws ezcSignalSlotException if the signals options has been set and $signal is not in the list of signals.
  368. * @param string $signal
  369. * @param callback $slot
  370. * @param int $priority
  371. * @return void
  372. */
  373. public function disconnect( $signal, $slot, $priority = null )
  374. {
  375. // if the the signals option is set we must check if the signal exists
  376. if ( $this->options->signals != null && !in_array( $signal, $this->options->signals ) )
  377. {
  378. throw new ezcSignalSlotException( "No such signal {$signal}" );
  379. }
  380. if ( $priority === null ) // delete first found, searched from back
  381. {
  382. $priorityKeys = array();
  383. if ( isset( $this->priorityConnections[$signal] ) )
  384. {
  385. $priorityKeys = array_keys( $this->priorityConnections[$signal] );
  386. }
  387. $allPriorities = array_unique( array_merge( $priorityKeys, array( 1000 ) /*default*/ ) );
  388. rsort( $allPriorities, SORT_NUMERIC );
  389. foreach ( $allPriorities as $priority )
  390. {
  391. if ( $priority === 1000 )
  392. {
  393. if ( isset( $this->defaultConnections[$signal] ) )
  394. {
  395. foreach ( $this->defaultConnections[$signal] as $key => $callback )
  396. {
  397. if ( ezcSignalCallbackComparer::compareCallbacks( $slot, $callback ) )
  398. {
  399. unset( $this->defaultConnections[$signal][$key] );
  400. return;
  401. }
  402. }
  403. }
  404. }
  405. else
  406. {
  407. if ( isset( $this->priorityConnections[$signal] ) &&
  408. isset( $this->priorityConnections[$signal][$priority] ) )
  409. {
  410. foreach ( $this->priorityConnections[$signal][$priority] as $key => $callback)
  411. {
  412. if ( ezcSignalCallbackComparer::compareCallbacks( $slot, $callback ) )
  413. {
  414. unset( $this->priorityConnections[$signal][$priority][$key] );
  415. // if the priority is empty now it should be unset
  416. if ( count( $this->priorityConnections[$signal][$priority] ) == 0 )
  417. {
  418. unset( $this->priorityConnections[$signal][$priority] );
  419. }
  420. return;
  421. }
  422. }
  423. }
  424. }
  425. }
  426. }
  427. else if ( $priority === 1000 ) // only delete from default
  428. {
  429. if ( isset( $this->defaultConnections[$signal] ) )
  430. {
  431. foreach ( $this->defaultConnections[$signal] as $key => $callback )
  432. {
  433. if ( ezcSignalCallbackComparer::compareCallbacks( $slot, $callback ) )
  434. {
  435. unset( $this->defaultConnections[$signal][$key] );
  436. return;
  437. }
  438. }
  439. }
  440. }
  441. else // delete from priority connectinos
  442. {
  443. if ( isset( $this->priorityConnections[$signal] ) &&
  444. isset( $this->priorityConnections[$signal][$priority] ) )
  445. {
  446. foreach ( $this->priorityConnections[$signal][$priority] as $key => $callback )
  447. {
  448. if ( ezcSignalCallbackComparer::compareCallbacks( $slot, $callback ) )
  449. {
  450. unset( $this->priorityConnections[$signal][$priority][$key] );
  451. // if the priority is empty now it should be unset
  452. if ( count( $this->priorityConnections[$signal][$priority] ) == 0 )
  453. {
  454. unset( $this->priorityConnections[$signal][$priority] );
  455. }
  456. return;
  457. }
  458. }
  459. }
  460. }
  461. }
  462. }
  463. ?>