1: <?php
2: /**
3: *
4: * @package XCube
5: * @version $Id: XCube_Delegate.class.php,v 1.9 2008/11/16 10:05:55 minahito Exp $
6: * @copyright Copyright 2005-2007 XOOPS Cube Project <https://github.com/xoopscube/legacy>
7: * @license https://github.com/xoopscube/legacy/blob/master/docs/bsd_licenses.txt Modified BSD license
8: *
9: */
10:
11: /**
12: * @public
13: * @brief [Final] This class is an expression of reference in delegation mechanism for PHP4.
14: *
15: * This class is adapt reference pointer for XCube_Delegate. Because XCube_Delegate is
16: * virtual function pointers, it's impossible to hand variables as references to
17: * XCube_Delegate::call(). In a such case, use this class as an adapter.
18: *
19: * \code
20: * $object = new Object;
21: * $delegate->call($object); // In PHP4, functions will receive the copied value of $object.
22: *
23: * $object = new Object;
24: * $delegate->call(new XCube_Delegate($object)); // In PHP4, functions will receive the object.
25: * \endcode
26: */
27: class XCube_Ref
28: {
29: /**
30: * @private
31: * @brief mixed
32: */
33: var $_mObject = null;
34:
35: /**
36: * @public Constructor.
37: * @param $obj mixed
38: */
39: function XCube_Ref(&$obj)
40: {
41: $this->_mObject =& $obj;
42: }
43:
44: /**
45: * @public
46: * @internal
47: * @brief [Secret Agreement] Gets the value which this class is adapting.
48: * @return mixed
49: * @attention
50: * Only XCube_Delegate & XCube_DelegateManager should call this method.
51: */
52: function &getObject()
53: {
54: return $this->_mObject;
55: }
56: }
57:
58: //
59: // Constants for delegate priority.
60: // But, developers should use {first,normal,firnal} basically.
61: //
62:
63: define("XCUBE_DELEGATE_PRIORITY_1", 10);
64: define("XCUBE_DELEGATE_PRIORITY_2", 20);
65: define("XCUBE_DELEGATE_PRIORITY_3", 30);
66: define("XCUBE_DELEGATE_PRIORITY_4", 40);
67: define("XCUBE_DELEGATE_PRIORITY_5", 50);
68: define("XCUBE_DELEGATE_PRIORITY_6", 60);
69: define("XCUBE_DELEGATE_PRIORITY_7", 70);
70: define("XCUBE_DELEGATE_PRIORITY_8", 80);
71: define("XCUBE_DELEGATE_PRIORITY_9", 90);
72: define("XCUBE_DELEGATE_PRIORITY_10", 100);
73:
74: define("XCUBE_DELEGATE_PRIORITY_FIRST", XCUBE_DELEGATE_PRIORITY_1);
75: define("XCUBE_DELEGATE_PRIORITY_NORMAL", XCUBE_DELEGATE_PRIORITY_5);
76: define("XCUBE_DELEGATE_PRIORITY_FINAL", XCUBE_DELEGATE_PRIORITY_10);
77:
78: /**
79: * @public
80: * @brief [Final] Used for the simple mechanism for common delegation in XCube.
81: *
82: * A delegate can have $callback as connected function, $filepath for lazy
83: * loading and $priority as order indicated.
84: *
85: * \par Priority
86: *
87: * Default of this parameter is XCUBE_DELEGATE_PRIORITY_NORMAL. Usually, this
88: * parameter isn't specified. Plus, the magic number should be used to specify
89: * priority. Use XCUBE_DELEGATE_PRIORITY_FIRST or XCUBE_DELEGATE_PRIORITY_FINAL
90: * with Addition and Subtraction. (e.x. XCUBE_DELEGATE_PRIORITY_NORMAL - 1 )
91: *
92: * @attention
93: * This is the candidate as new delegate style, which has foolish name to escape
94: * conflict with old XCube_Delegate. After replacing, we'll change all.
95: */
96: class XCube_Delegate
97: {
98: /**
99: * @private
100: * @brief Vector Array - The list of type of parameters.
101: */
102: var $_mSignatures = array();
103:
104: /**
105: * @private
106: * @brief Complex Array - This is Array for callback type data.
107: */
108: var $_mCallbacks = array();
109:
110: /**
111: * @private
112: * @brief bool
113: */
114: var $_mHasCheckSignatures = false;
115:
116: /**
117: * @private
118: * @brief bool
119: *
120: * If register() is failed, this flag become true. That problem is raised,
121: * when register() is called before $root come to have the delegate
122: * manager.
123: *
124: * @var bool
125: */
126: var $_mIsLazyRegister = false;
127:
128: /**
129: * @private
130: * @brief string - This is register name for lazy registering.
131: */
132: var $_mLazyRegisterName = null;
133:
134: /**
135: * @private
136: */
137: var $_mUniqueID;
138:
139: /**
140: * @public
141: * @brief Constructor.
142: *
143: * The parameter of the constructor is a variable argument style to specify
144: * the sigunature of this delegate. If the argument is empty, signature checking
145: * doesn't work. Empty arguments are good to use in many cases. But, that's
146: * important to accent a delegate for making rightly connected functions.
147: *
148: * \code
149: * $delegate =new XCube_Delegate("string", "string");
150: * \endcode
151: */
152: function XCube_Delegate()
153: {
154: if (func_num_args()) {
155: $this->_setSignatures(func_get_args());
156: }
157: $this->_mUniqueID = uniqid(rand(), true);
158: }
159:
160: /**
161: * @private
162: * @brief Set signatures for this delegate.
163: * @param $args Vector Array - std::vector<string>
164: * @return void
165: *
166: * By this method, this function will come to check arguments with following
167: * signatures at call().
168: */
169: function _setSignatures($args)
170: {
171: $this->_mSignatures =& $args;
172: for ($i=0, $max=count($args); $i<$max ; $i++) {
173: $arg = $args[$i];
174: $idx = strpos($arg, ' &');
175: if ($idx !== false) $args[$i] = substr($arg, 0, $idx);
176: }
177: $this->_mHasCheckSignatures = true;
178: }
179:
180: /**
181: * @public
182: * @brief Registers this object to delegate manager of root.
183: * @param $delegateName string
184: * @return bool
185: */
186: function register($delegateName)
187: {
188: $root =& XCube_Root::getSingleton();
189: if ($root->mDelegateManager != null) {
190: $this->_mIsLazyRegister = false;
191: $this->_mLazyRegisterName = null;
192:
193: return $root->mDelegateManager->register($delegateName, $this);
194: }
195:
196: $this->_mIsLazyRegister = true;
197: $this->_mLazyRegisterName = $delegateName;
198:
199: return false;
200: }
201:
202: /**
203: * @public
204: * @brief [Overload] Connects functions to this object as callbacked functions
205: * @return void
206: *
207: * This method is virtual overload by sigunatures.
208: *
209: * \code
210: * add(callback $callback, int priority = XCUBE_DELEGATE_PRIORITY_NORMAL);
211: * add(callback $callback, string filepath = null);
212: * add(callback $callback, int priority =... , string filepath=...);
213: * \endcode
214: */
215: function add($callback, $param2 = null, $param3 = null)
216: {
217: $priority = XCUBE_DELEGATE_PRIORITY_NORMAL;
218: $filepath = null;
219:
220: if (!is_array($callback) && strstr($callback, '::')) {
221: if (count($tmp = explode('::', $callback)) == 2) $callback = $tmp;
222: }
223:
224: if ($param2 !== null) {
225: if (is_int($param2)) {
226: $priority = $param2;
227: $filepath = ($param3 !== null && is_string($param3)) ? $param3 : null;
228: } elseif (is_string($param2)) {
229: $filepath = $param2;
230: }
231: }
232:
233: $this->_mCallbacks[$priority][] = array($callback, $filepath);
234: ksort($this->_mCallbacks);
235: }
236:
237: /**
238: * @public
239: * @brief Disconnects a function from this object.
240: * @return void
241: */
242: function delete($delcallback)
243: {
244: foreach (array_keys($this->_mCallbacks) as $priority) {
245: foreach (array_keys($this->_mCallbacks[$priority]) as $idx) {
246: $callback = $this->_mCallbacks[$priority][$idx][0];
247: if (XCube_DelegateUtils::_compareCallback($callback, $delcallback)) {
248: unset($this->_mCallbacks[$priority][$idx]);
249: }
250: if (count($this->_mCallbacks[$priority])==0) {
251: unset($this->_mCallbacks[$priority]);
252: }
253: }
254: }
255: }
256:
257: /**
258: * @public
259: * @brief Resets all delegate functions from this object.
260: * @return void
261: * @attention
262: * This is the special method, so XCube doesn't recommend using this.
263: */
264: function reset()
265: {
266: unset($this->_mCallbacks);
267: $this->_mCallbacks = array();
268: }
269:
270: /**
271: * @public
272: * @brief Calls connected functions of this object.
273: */
274: function call()
275: {
276: $args = func_get_args();
277: $num = func_num_args();
278:
279: if ($this->_mIsLazyRegister) {
280: $this->register($this->_mLazyRegisterName);
281: }
282:
283: if ($hasSig = $this->_mHasCheckSignatures) {
284: if (count($mSigs = &$this->_mSignatures) != $num) return false;
285: }
286:
287: for ($i=0 ; $i<$num ;$i++) {
288: $arg = &$args[$i];
289: if ($arg instanceof XCube_Ref) $args[$i] =& $arg->getObject();
290:
291: if ($hasSig) {
292: if (!isset($mSigs[$i])) return false;
293: switch ($mSigs[$i]) {
294: case 'void':
295: break;
296:
297: case 'bool':
298: if (!empty($arg)) $args[$i] = $arg? true : false;
299: break;
300:
301: case 'int':
302: if (!empty($arg)) $args[$i] = (int)$arg;
303: break;
304:
305: case 'float':
306: if (!empty($arg)) $args[$i] = (float)$arg;
307: break;
308:
309: case 'string':
310: if (!empty($arg) && !is_string($arg)) return false;
311: break;
312:
313: default:
314: if (!is_a($arg, $mSigs[$i])) return false;
315: }
316: }
317: }
318:
319: foreach ($this->_mCallbacks as $callback_arrays) {
320: foreach ($callback_arrays as $callback_array) {
321: list($callback, $file) = $callback_array;
322:
323: if ($file) require_once $file;
324: if (is_callable($callback)) call_user_func_array($callback, $args);
325: }
326: }
327: }
328:
329: /**
330: * @public
331: * @brief Gets a value indicating whether this object has callback functions.
332: * @return bool
333: */
334: function isEmpty()
335: {
336: return (count($this->_mCallbacks) == 0);
337: }
338:
339: /**
340: * @public
341: * @internal
342: * @brief Gets the unique ID of this object.
343: * @attention
344: * This is the special method, so XCube doesn't recommend using this.
345: */
346: function getID()
347: {
348: return $this->_mUniqueID;
349: }
350: }
351:
352: /**
353: * @public
354: * @brief Manages for delegates.
355: *
356: * This is the agent of un-registered delegate objects. Usually, connected
357: * functions can't be added to un-registered delegates. When destination
358: * delegates are un-registered yet, this manager is keeping those functions
359: * and parameters until the destination delegate will be registered.
360: *
361: * In other words, this class realizes lazy delegate registering.
362: */
363: class XCube_DelegateManager
364: {
365: /**
366: * @protected
367: * @brief Complex Array
368: */
369: var $_mCallbacks = array();
370:
371: /**
372: * @protected
373: * @brief Complex Array
374: */
375: var $_mCallbackParameters = array();
376:
377: /**
378: * @protected
379: * @brief Map Array - std::map<string, XCube_Delegate*>
380: */
381: var $_mDelegates = array();
382:
383: /**
384: * @public
385: * @brief Constructor.
386: */
387: function XCube_DelegateManager()
388: {
389: }
390:
391: /**
392: * @public
393: * @brief Adds $delegate as Delegate to the list of this manager.
394: * @param $name string - Registration name.
395: * @param $delegate XCube_Delegate - Delegate object which will be registered.
396: * @return bool
397: *
398: * If some functions that want to connect to $delegate, have been entrusted yet,
399: * this object calls add() of $delegate with their parameters.
400: *
401: * Usually this member function isn't used as Cube's API by developers. In many
402: * cases, XCube_Delegate::register() calls this.
403: */
404: function register($name, &$delegate)
405: {
406: $mDelegate =& $this->_mDelegates[$name];
407: if (isset($mDelegate[$id=$delegate->getID()])) {
408: return false;
409: }
410: else {
411: $mDelegate[$id] =& $delegate;
412:
413: $mcb = &$this->_mCallbacks[$name];
414: if (isset($mcb) && count($mcb) > 0) {
415: foreach ($mcb as $key=>$func) {
416: list($a, $b) = $this->_mCallbackParameters[$name][$key];
417: $delegate->add($func, $a, $b);
418: }
419: }
420:
421: return true;
422: }
423: }
424:
425: /**
426: * @public
427: * @brief Connects functions to the delegate that have the specified name.
428: * @param $name string - Registration name.
429: * @return void
430: *
431: * If there aren't any delegates that have the specified name, this manager
432: * entrust parameters to member properties. Then, when the delegate that
433: * have the specified name will be registered, this manager will set these
434: * parameters to the delegate.
435: *
436: * @see XCube_Delegate::add()
437: */
438: function add($name, $callback, $param3 = null, $param4 = null)
439: {
440: if (isset($this->_mDelegates[$name])) {
441: foreach($this->_mDelegates[$name] as $func) {
442: $func->add($callback, $param3, $param4);
443: }
444: }
445: $this->_mCallbacks[$name][] = $callback;
446: $this->_mCallbackParameters[$name][] = array('0' => $param3, '1' => $param4);
447: }
448:
449: /**
450: * @public
451: * @param $name string - Registration name
452: * @brief Disconnects a function from the delegate that have the specified name.
453: * @see XCube_Delegate::delete()
454: */
455: function delete($name, $delcallback)
456: {
457: if (isset($this->_mDelegates[$name])) {
458: foreach(array_keys($this->_mDelegates[$name]) as $key) {
459: $this->_mDelegates[$name][$key]->delete($delcallback);
460: }
461: }
462: if (isset($this->_mCallbacks[$name])) {
463: foreach(array_keys($this->_mCallbacks[$name]) as $key) {
464: $callback = $this->_mCallbacks[$name][$key];
465: if (XCube_DelegateUtils::_compareCallback($callback, $delcallback)) {
466: unset($this->_mCallbacks[$name][$key]);
467: unset($this->_mCallbackParameters[$name][$key]);
468: }
469: }
470: }
471: }
472:
473: /**
474: * @public
475: * @brief Resets all functions off the delegate that have the specified name.
476: * @param $name string - Registration name which will be resetted.
477: *
478: * @see XCube_Delegate::reset()
479: */
480: function reset($name)
481: {
482: if (isset($this->_mDelegates[$name])) {
483: foreach(array_keys($this->_mDelegates[$name]) as $key) {
484: $this->_mDelegates[$name][$key]->reset();
485: }
486: }
487: if (isset($this->_mCallbacks[$name])) {
488: unset($this->_mCallbacks[$name]);
489: unset($this->_mCallbackParameters[$name]);
490: }
491: }
492:
493: /**
494: * @public
495: * @brief Gets a value indicating whether the specified delegate has callback functions.
496: * @param string $name string - Registration name.
497: * @return bool
498: */
499: function isEmpty($name)
500: {
501: if (isset($this->_mDelegates[$name])) {
502: return $this->_mDelegates[$name]->isEmpty();
503: }
504:
505: return isset($this->_mCallbacks[$name]) ? (count($this->_mCallbacks[$name]) == 0) : false;
506: }
507:
508:
509: /**
510: * @public
511: * @return Map Array - std::map<string, XCube_Delegate*>
512: */
513: function getDelegates()
514: {
515: return $this->_mDelegates;
516: }
517: }
518:
519: /**
520: * @public
521: * @brief Utility class which collects utility functions for delegates.
522: *
523: * XCube_DelegateUtils::call("Delegate Name"[, fuction args...]); \n
524: * XCube_DelegateUtils::raiseEvent("Event Name"[, fuction params...]); \n
525: * $string = XCube_DelegateUtils::applyStringFilter("Filter Name", $string, [, option params...]); \n
526: */
527: class XCube_DelegateUtils
528: {
529: /**
530: * @private
531: * @brief Private Construct. In other words, it's possible to create an instance of this class.
532: */
533: function XCube_DelegateUtils()
534: {
535: }
536:
537: /**
538: * @deprecated raiseEvent()
539: */
540: static function call()
541: {
542: $args = func_get_args();
543: $num = func_num_args();
544: if ($num == 1) $delegateName = $args[0];
545: elseif ($num) $delegateName = array_shift($args);
546: else return false;
547: $m =& XCube_Root::getSingleton()->mDelegateManager;
548: if ($m) {
549: $delegates = $m->getDelegates();
550: if (isset($delegates[$delegateName])) {
551: $delegates = &$delegates[$delegateName];
552: list($key) = array_keys($delegates);
553: $delegate =& $delegates[$key];
554: } else {
555: $delegate = new XCube_Delegate;
556: $m->register($delegateName, $delegate);
557: }
558: }
559: return call_user_func_array(array(&$delegate,'call'),$args);
560: }
561:
562: /**
563: * @public
564: * @brief [Static] Utility method for calling event-delegates.
565: *
566: * This method is a shortcut for calling delegates without actual delegate objects.
567: * If there is not the delegate specified by the 1st parameter, the delegate will
568: * be made right now. Therefore, this method is helpful for events.
569: *
570: * @note
571: * \code
572: * XCube_DelegateUtils::raiseEvent("Module.A.Exception.Null");
573: * \endcode
574: *
575: * The uppering code equals the following code;
576: *
577: * \code
578: * {
579: * $local =new XCube_Delegate();
580: * $local->register("Module.A.Exception.Null");
581: * $local->call();
582: * }
583: * \endcode
584: *
585: * @attention
586: * Only event-owners should use this method. Outside program never calls other's
587: * events. This is a kind of XCube_Delegate rules. There is the following code;
588: *
589: * \code
590: * ClassA::check()
591: * {
592: * if ($this->mThing == null)
593: * XCube_DelegateUtils::raiseEvent("Module.A.Exception.Null");
594: * }
595: * \endcode
596: *
597: * In this case, other class never calls the event.
598: *
599: * \code
600: * //
601: * // NEVER writes the following code;
602: * //
603: * $obj = new ClassA();
604: * if ($obj->mThing == null)
605: * XCube_DelegateUtils::raiseEvent("Module.A.Exception.Null");
606: * \endcode
607: *
608: * Other classes may call only ClassA::check();
609: *
610: * @param 1st Delaget Name
611: * @param 2nd and more : Delegate function parameters
612: * @return bool
613: */
614: function raiseEvent()
615: {
616: if (func_num_args()) {
617: $args = func_get_args();
618: return call_user_func_array(array('XCube_DelegateUtils','call'), $args);
619: }
620: }
621:
622: /**
623: * @public
624: * @internal
625: * @brief [Static] Calls a delegate string filter function. This method is multi-parameters.
626: *
627: * This is a special shortcut for processing string filter.
628: *
629: * @param 1st string - Delaget Name
630: * @param 2nd string
631: * @param 3rd and more - Optional function paramaters
632: * @return string
633: */
634: function applyStringFilter()
635: {
636: $args = func_get_args();
637: $num = func_num_args();
638: if ($num > 1) {
639: $delegateName = $args[0];
640: $string = $args[1];
641: if (!empty($string) && is_string($string)) {
642: return "";
643: }
644: $args[1] = new XCube_Ref($string);
645: call_user_func_array(array('XCube_DelegateUtils','call'),$args);
646: return $string;
647: } else {
648: return "";
649: }
650: }
651:
652: /**
653: * @public
654: * @internal
655: * @brief [Static][Secret Agreement] Comparing two callback (PHP4 cannot compare Object exactly)
656: * @param $callback1 : callback
657: * @param $callback2 : callback
658: * @return bool
659: *
660: * @attention
661: * Only XCube_Delegate, XCube_DelegateManager and sub-classes of them should use this method.
662: */
663: function _compareCallback($callback1, $callback2)
664: {
665: if (!is_array($callback1) && !is_array($callback2) && ($callback1 === $callback2)) {
666: return true;
667: } elseif (is_array($callback1) && is_array($callback2) && (gettype($callback1[0]) === gettype($callback2[0]))
668: && ($callback1[1] === $callback2[1])) {
669: if (!is_object($callback1[0]) && ($callback1[0] === $callback2[0])) {
670: return true;
671: } elseif (is_object($callback1[0]) && (get_class($callback1[0]) === get_class($callback2[0]))) {
672: return true;
673: }
674: }
675: return false;
676: }
677: }
678: ?>
679: