Subversion-Projekte lars-tiefland.prado

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * TComponent, TPropertyValue classes
4
 *
5
 * @author Qiang Xue <qiang.xue@gmail.com>
6
 * @link http://www.pradosoft.com/
7
 * @copyright Copyright &copy; 2005-2008 PradoSoft
8
 * @license http://www.pradosoft.com/license/
9
 * @version $Id: TComponent.php 2541 2008-10-21 15:05:13Z qiang.xue $
10
 * @package System
11
 */
12
 
13
/**
14
 * TComponent class
15
 *
16
 * TComponent is the base class for all PRADO components.
17
 * TComponent implements the protocol of defining, using properties and events.
18
 *
19
 * A property is defined by a getter method, and/or a setter method.
20
 * Properties can be accessed in the way like accessing normal object members.
21
 * Reading or writing a property will cause the invocation of the corresponding
22
 * getter or setter method, e.g.,
23
 * <code>
24
 * $a=$this->Text;     // equivalent to $a=$this->getText();
25
 * $this->Text='abc';  // equivalent to $this->setText('abc');
26
 * </code>
27
 * The signatures of getter and setter methods are as follows,
28
 * <code>
29
 * // getter, defines a readable property 'Text'
30
 * function getText() { ... }
31
 * // setter, defines a writable property 'Text', with $value being the value to be set to the property
32
 * function setText($value) { ... }
33
 * </code>
34
 * Property names are case-insensitive. It is recommended that they are written
35
 * in the format of concatenated words, with the first letter of each word
36
 * capitalized (e.g. DisplayMode, ItemStyle).
37
 *
38
 * An event is defined by the presence of a method whose name starts with 'on'.
39
 * The event name is the method name and is thus case-insensitive.
40
 * An event can be attached with one or several methods (called event handlers).
41
 * An event can be raised by calling {@link raiseEvent} method, upon which
42
 * the attached event handlers will be invoked automatically in the order they
43
 * are attached to the event. Event handlers must have the following signature,
44
 * <code>
45
 * function eventHandlerFuncName($sender,$param) { ... }
46
 * </code>
47
 * where $sender refers to the object who is responsible for the raising of the event,
48
 * and $param refers to a structure that may contain event-specific information.
49
 * To raise an event (assuming named as 'Click') of a component, use
50
 * <code>
51
 * $component->raiseEvent('OnClick');
52
 * </code>
53
 * To attach an event handler to an event, use one of the following ways,
54
 * <code>
55
 * $component->OnClick=$callback;  // or $component->OnClick->add($callback);
56
 * $$component->attachEventHandler('OnClick',$callback);
57
 * </code>
58
 * The first two ways make use of the fact that $component->OnClick refers to
59
 * the event handler list {@link TList} for the 'OnClick' event.
60
 * The variable $callback contains the definition of the event handler that can
61
 * be either a string referring to a global function name, or an array whose
62
 * first element refers to an object and second element a method name/path that
63
 * is reachable by the object, e.g.
64
 * - 'buttonClicked' : buttonClicked($sender,$param);
65
 * - array($object,'buttonClicked') : $object->buttonClicked($sender,$param);
66
 * - array($object,'MainContent.SubmitButton.buttonClicked') :
67
 *   $object->MainContent->SubmitButton->buttonClicked($sender,$param);
68
 *
69
 * @author Qiang Xue <qiang.xue@gmail.com>
70
 * @version $Id: TComponent.php 2541 2008-10-21 15:05:13Z qiang.xue $
71
 * @package System
72
 * @since 3.0
73
 */
74
class TComponent
75
{
76
	/**
77
	 * @var array event handler lists
78
	 */
79
	private $_e=array();
80
 
81
	/**
82
	 * Returns a property value or an event handler list by property or event name.
83
	 * Do not call this method. This is a PHP magic method that we override
84
	 * to allow using the following syntax to read a property:
85
	 * <code>
86
	 * $value=$component->PropertyName;
87
	 * </code>
88
	 * and to obtain the event handler list for an event,
89
	 * <code>
90
	 * $eventHandlerList=$component->EventName;
91
	 * </code>
92
	 * @param string the property name or the event name
93
	 * @return mixed the property value or the event handler list
94
	 * @throws TInvalidOperationException if the property/event is not defined.
95
	 */
96
	public function __get($name)
97
	{
98
		$getter='get'.$name;
99
		if(method_exists($this,$getter))
100
		{
101
			// getting a property
102
			return $this->$getter();
103
		}
104
		else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
105
		{
106
			// getting an event (handler list)
107
			$name=strtolower($name);
108
			if(!isset($this->_e[$name]))
109
				$this->_e[$name]=new TList;
110
			return $this->_e[$name];
111
		}
112
		else
113
		{
114
			throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);
115
		}
116
	}
117
 
118
	/**
119
	 * Sets value of a component property.
120
	 * Do not call this method. This is a PHP magic method that we override
121
	 * to allow using the following syntax to set a property or attach an event handler.
122
	 * <code>
123
	 * $this->PropertyName=$value;
124
	 * $this->EventName=$handler;
125
	 * </code>
126
	 * @param string the property name or event name
127
	 * @param mixed the property value or event handler
128
	 * @throws TInvalidOperationException If the property is not defined or read-only.
129
	 */
130
	public function __set($name,$value)
131
	{
132
		$setter='set'.$name;
133
		if(method_exists($this,$setter))
134
		{
135
			$this->$setter($value);
136
		}
137
		else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
138
		{
139
			$this->attachEventHandler($name,$value);
140
		}
141
		else if(method_exists($this,'get'.$name))
142
		{
143
			throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
144
		}
145
		else
146
		{
147
			throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);
148
		}
149
	}
150
 
151
	/**
152
	 * Determines whether a property is defined.
153
	 * A property is defined if there is a getter or setter method
154
	 * defined in the class. Note, property names are case-insensitive.
155
	 * @param string the property name
156
	 * @return boolean whether the property is defined
157
	 */
158
	public function hasProperty($name)
159
	{
160
		return method_exists($this,'get'.$name) || method_exists($this,'set'.$name);
161
	}
162
 
163
	/**
164
	 * Determines whether a property can be read.
165
	 * A property can be read if the class has a getter method
166
	 * for the property name. Note, property name is case-insensitive.
167
	 * @param string the property name
168
	 * @return boolean whether the property can be read
169
	 */
170
	public function canGetProperty($name)
171
	{
172
		return method_exists($this,'get'.$name);
173
	}
174
 
175
	/**
176
	 * Determines whether a property can be set.
177
	 * A property can be written if the class has a setter method
178
	 * for the property name. Note, property name is case-insensitive.
179
	 * @param string the property name
180
	 * @return boolean whether the property can be written
181
	 */
182
	public function canSetProperty($name)
183
	{
184
		return method_exists($this,'set'.$name);
185
	}
186
 
187
	/**
188
	 * Evaluates a property path.
189
	 * A property path is a sequence of property names concatenated by '.' character.
190
	 * For example, 'Parent.Page' refers to the 'Page' property of the component's
191
	 * 'Parent' property value (which should be a component also).
192
	 * @param string property path
193
	 * @return mixed the property path value
194
	 */
195
	public function getSubProperty($path)
196
	{
197
		$object=$this;
198
		foreach(explode('.',$path) as $property)
199
			$object=$object->$property;
200
		return $object;
201
	}
202
 
203
	/**
204
	 * Sets a value to a property path.
205
	 * A property path is a sequence of property names concatenated by '.' character.
206
	 * For example, 'Parent.Page' refers to the 'Page' property of the component's
207
	 * 'Parent' property value (which should be a component also).
208
	 * @param string property path
209
	 * @param mixed the property path value
210
	 */
211
	public function setSubProperty($path,$value)
212
	{
213
		$object=$this;
214
		if(($pos=strrpos($path,'.'))===false)
215
			$property=$path;
216
		else
217
		{
218
			$object=$this->getSubProperty(substr($path,0,$pos));
219
			$property=substr($path,$pos+1);
220
		}
221
		$object->$property=$value;
222
	}
223
 
224
	/**
225
	 * Determines whether an event is defined.
226
	 * An event is defined if the class has a method whose name is the event name prefixed with 'on'.
227
	 * Note, event name is case-insensitive.
228
	 * @param string the event name
229
	 * @return boolean
230
	 */
231
	public function hasEvent($name)
232
	{
233
		return strncasecmp($name,'on',2)===0 && method_exists($this,$name);
234
	}
235
 
236
	/**
237
	 * @return boolean whether an event has been attached one or several handlers
238
	 */
239
	public function hasEventHandler($name)
240
	{
241
		$name=strtolower($name);
242
		return isset($this->_e[$name]) && $this->_e[$name]->getCount()>0;
243
	}
244
 
245
	/**
246
	 * Returns the list of attached event handlers for an event.
247
	 * @return TList list of attached event handlers for an event
248
	 * @throws TInvalidOperationException if the event is not defined
249
	 */
250
	public function getEventHandlers($name)
251
	{
252
		if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
253
		{
254
			$name=strtolower($name);
255
			if(!isset($this->_e[$name]))
256
				$this->_e[$name]=new TList;
257
			return $this->_e[$name];
258
		}
259
		else
260
			throw new TInvalidOperationException('component_event_undefined',get_class($this),$name);
261
	}
262
 
263
	/**
264
	 * Attaches an event handler to an event.
265
	 *
266
	 * The handler must be a valid PHP callback, i.e., a string referring to
267
	 * a global function name, or an array containing two elements with
268
	 * the first element being an object and the second element a method name
269
	 * of the object. In Prado, you can also use method path to refer to
270
	 * an event handler. For example, array($object,'Parent.buttonClicked')
271
	 * uses a method path that refers to the method $object->Parent->buttonClicked(...).
272
	 *
273
	 * The event handler must be of the following signature,
274
	 * <code>
275
	 * function handlerName($sender,$param) {}
276
	 * </code>
277
	 * where $sender represents the object that raises the event,
278
	 * and $param is the event parameter.
279
	 *
280
	 * This is a convenient method to add an event handler.
281
	 * It is equivalent to {@link getEventHandlers}($name)->add($handler).
282
	 * For complete management of event handlers, use {@link getEventHandlers}
283
	 * to get the event handler list first, and then do various
284
	 * {@link TList} operations to append, insert or remove
285
	 * event handlers. You may also do these operations like
286
	 * getting and setting properties, e.g.,
287
	 * <code>
288
	 * $component->OnClick[]=array($object,'buttonClicked');
289
	 * $component->OnClick->insertAt(0,array($object,'buttonClicked'));
290
	 * </code>
291
	 * which are equivalent to the following
292
	 * <code>
293
	 * $component->getEventHandlers('OnClick')->add(array($object,'buttonClicked'));
294
	 * $component->getEventHandlers('OnClick')->insertAt(0,array($object,'buttonClicked'));
295
	 * </code>
296
	 *
297
	 * @param string the event name
298
	 * @param callback the event handler
299
	 * @throws TInvalidOperationException if the event does not exist
300
	 */
301
	public function attachEventHandler($name,$handler)
302
	{
303
		$this->getEventHandlers($name)->add($handler);
304
	}
305
 
306
	/**
307
	 * Detaches an existing event handler.
308
	 * This method is the opposite of {@link attachEventHandler}.
309
	 * @param string event name
310
	 * @param callback the event handler to be removed
311
	 * @return boolean if the removal is successful
312
	 */
313
	public function detachEventHandler($name,$handler)
314
	{
315
		if($this->hasEventHandler($name))
316
		{
317
			try
318
			{
319
				$this->getEventHandlers($name)->remove($handler);
320
				return true;
321
			}
322
			catch(Exception $e)
323
			{
324
			}
325
		}
326
		return false;
327
	}
328
 
329
	/**
330
	 * Raises an event.
331
	 * This method represents the happening of an event and will
332
	 * invoke all attached event handlers for the event.
333
	 * @param string the event name
334
	 * @param mixed the event sender object
335
	 * @param TEventParameter the event parameter
336
	 * @throws TInvalidOperationException if the event is undefined
337
	 * @throws TInvalidDataValueException If an event handler is invalid
338
	 */
339
	public function raiseEvent($name,$sender,$param)
340
	{
341
		$name=strtolower($name);
342
		if(isset($this->_e[$name]))
343
		{
344
			foreach($this->_e[$name] as $handler)
345
			{
346
				if(is_string($handler))
347
				{
348
					if(($pos=strrpos($handler,'.'))!==false)
349
					{
350
						$object=$this->getSubProperty(substr($handler,0,$pos));
351
						$method=substr($handler,$pos+1);
352
						if(method_exists($object,$method))
353
							$object->$method($sender,$param);
354
						else
355
							throw new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,$handler);
356
					}
357
					else
358
						call_user_func($handler,$sender,$param);
359
				}
360
				else if(is_callable($handler,true))
361
				{
362
					// an array: 0 - object, 1 - method name/path
363
					list($object,$method)=$handler;
364
					if(is_string($object))	// static method call
365
						call_user_func($handler,$sender,$param);
366
					else
367
					{
368
						if(($pos=strrpos($method,'.'))!==false)
369
						{
370
							$object=$this->getSubProperty(substr($method,0,$pos));
371
							$method=substr($method,$pos+1);
372
						}
373
						if(method_exists($object,$method))
374
							$object->$method($sender,$param);
375
						else
376
							throw new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,$handler[1]);
377
					}
378
				}
379
				else
380
					throw new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,gettype($handler));
381
			}
382
		}
383
		else if(!$this->hasEvent($name))
384
			throw new TInvalidOperationException('component_event_undefined',get_class($this),$name);
385
	}
386
 
387
	/**
388
	 * Evaluates a PHP expression in the context of this control.
389
	 * @return mixed the expression result
390
	 * @throws TInvalidOperationException if the expression is invalid
391
	 */
392
	public function evaluateExpression($expression)
393
	{
394
		try
395
		{
396
			if(eval("\$result=$expression;")===false)
397
				throw new Exception('');
398
			return $result;
399
		}
400
		catch(Exception $e)
401
		{
402
			throw new TInvalidOperationException('component_expression_invalid',get_class($this),$expression,$e->getMessage());
403
		}
404
	}
405
 
406
	/**
407
	 * Evaluates a list of PHP statements.
408
	 * @param string PHP statements
409
	 * @return string content echoed or printed by the PHP statements
410
	 * @throws TInvalidOperationException if the statements are invalid
411
	 */
412
	public function evaluateStatements($statements)
413
	{
414
		try
415
		{
416
			ob_start();
417
			if(eval($statements)===false)
418
				throw new Exception('');
419
			$content=ob_get_contents();
420
			ob_end_clean();
421
			return $content;
422
		}
423
		catch(Exception $e)
424
		{
425
			throw new TInvalidOperationException('component_statements_invalid',get_class($this),$statements,$e->getMessage());
426
		}
427
	}
428
 
429
	/**
430
	 * This method is invoked after the component is instantiated by a template.
431
	 * When this method is invoked, the component's properties have been initialized.
432
	 * The default implementation of this method will invoke
433
	 * the potential parent component's {@link addParsedObject}.
434
	 * This method can be overridden.
435
	 * @param TComponent potential parent of this control
436
	 * @see addParsedObject
437
	 */
438
	public function createdOnTemplate($parent)
439
	{
440
		$parent->addParsedObject($this);
441
	}
442
 
443
	/**
444
	 * Processes an object that is created during parsing template.
445
	 * The object can be either a component or a static text string.
446
	 * This method can be overridden to customize the handling of newly created objects in template.
447
	 * Only framework developers and control developers should use this method.
448
	 * @param string|TComponent text string or component parsed and instantiated in template
449
	 * @see createdOnTemplate
450
	 */
451
	public function addParsedObject($object)
452
	{
453
	}
454
}
455
 
456
/**
457
 * TEnumerable class.
458
 * TEnumerable is the base class for all enumerable types.
459
 * To define an enumerable type, extend TEnumberable and define string constants.
460
 * Each constant represents an enumerable value.
461
 * The constant name must be the same as the constant value.
462
 * For example,
463
 * <code>
464
 * class TTextAlign extends TEnumerable
465
 * {
466
 *     const Left='Left';
467
 *     const Right='Right';
468
 * }
469
 * </code>
470
 * Then, one can use the enumerable values such as TTextAlign::Left and
471
 * TTextAlign::Right.
472
 *
473
 * @author Qiang Xue <qiang.xue@gmail.com>
474
 * @version $Id: TComponent.php 2541 2008-10-21 15:05:13Z qiang.xue $
475
 * @package System
476
 * @since 3.0
477
 */
478
class TEnumerable
479
{
480
}
481
 
482
/**
483
 * TPropertyValue class
484
 *
485
 * TPropertyValue is a utility class that provides static methods
486
 * to convert component property values to specific types.
487
 *
488
 * TPropertyValue is commonly used in component setter methods to ensure
489
 * the new property value is of specific type.
490
 * For example, a boolean-typed property setter method would be as follows,
491
 * <code>
492
 * function setPropertyName($value) {
493
 *     $value=TPropertyValue::ensureBoolean($value);
494
 *     // $value is now of boolean type
495
 * }
496
 * </code>
497
 *
498
 * Properties can be of the following types with specific type conversion rules:
499
 * - string: a boolean value will be converted to 'true' or 'false'.
500
 * - boolean: string 'true' (case-insensitive) will be converted to true,
501
 *            string 'false' (case-insensitive) will be converted to false.
502
 * - integer
503
 * - float
504
 * - array: string starting with '(' and ending with ')' will be considered as
505
 *          as an array expression and will be evaluated. Otherwise, an array
506
 *          with the value to be ensured is returned.
507
 * - object
508
 * - enum: enumerable type, represented by an array of strings.
509
 *
510
 * @author Qiang Xue <qiang.xue@gmail.com>
511
 * @version $Id: TComponent.php 2541 2008-10-21 15:05:13Z qiang.xue $
512
 * @package System
513
 * @since 3.0
514
 */
515
class TPropertyValue
516
{
517
	/**
518
	 * Converts a value to boolean type.
519
	 * Note, string 'true' (case-insensitive) will be converted to true,
520
	 * string 'false' (case-insensitive) will be converted to false.
521
	 * If a string represents a non-zero number, it will be treated as true.
522
	 * @param mixed the value to be converted.
523
	 * @return boolean
524
	 */
525
	public static function ensureBoolean($value)
526
	{
527
		if (is_string($value))
528
			return strcasecmp($value,'true')==0 || $value!=0;
529
		else
530
			return (boolean)$value;
531
	}
532
 
533
	/**
534
	 * Converts a value to string type.
535
	 * Note, a boolean value will be converted to 'true' if it is true
536
	 * and 'false' if it is false.
537
	 * @param mixed the value to be converted.
538
	 * @return string
539
	 */
540
	public static function ensureString($value)
541
	{
542
		if (is_bool($value))
543
			return $value?'true':'false';
544
		else
545
			return (string)$value;
546
	}
547
 
548
	/**
549
	 * Converts a value to integer type.
550
	 * @param mixed the value to be converted.
551
	 * @return integer
552
	 */
553
	public static function ensureInteger($value)
554
	{
555
		return (integer)$value;
556
	}
557
 
558
	/**
559
	 * Converts a value to float type.
560
	 * @param mixed the value to be converted.
561
	 * @return float
562
	 */
563
	public static function ensureFloat($value)
564
	{
565
		return (float)$value;
566
	}
567
 
568
	/**
569
	 * Converts a value to array type. If the value is a string and it is
570
	 * in the form (a,b,c) then an array consisting of each of the elements
571
	 * will be returned. If the value is a string and it is not in this form
572
	 * then an array consisting of just the string will be returned. If the value
573
	 * is not a string then
574
	 * @param mixed the value to be converted.
575
	 * @return array
576
	 */
577
	public static function ensureArray($value)
578
	{
579
		if(is_string($value))
580
		{
581
			$value = trim($value);
582
			$len = strlen($value);
583
			if ($len >= 2 && $value[0] == '(' && $value[$len-1] == ')')
584
			{
585
				eval('$array=array'.$value.';');
586
				return $array;
587
			}
588
			else
589
				return $len>0?array($value):array();
590
		}
591
		else
592
			return (array)$value;
593
	}
594
 
595
	/**
596
	 * Converts a value to object type.
597
	 * @param mixed the value to be converted.
598
	 * @return object
599
	 */
600
	public static function ensureObject($value)
601
	{
602
		return (object)$value;
603
	}
604
 
605
	/**
606
	 * Converts a value to enum type.
607
	 *
608
	 * This method checks if the value is of the specified enumerable type.
609
	 * A value is a valid enumerable value if it is equal to the name of a constant
610
	 * in the specified enumerable type (class).
611
	 * For more details about enumerable, see {@link TEnumerable}.
612
	 *
613
	 * For backward compatibility, this method also supports sanity
614
	 * check of a string value to see if it is among the given list of strings.
615
	 * @param mixed the value to be converted.
616
	 * @param mixed class name of the enumerable type, or array of valid enumeration values. If this is not an array,
617
	 * the method considers its parameters are of variable length, and the second till the last parameters are enumeration values.
618
	 * @return string the valid enumeration value
619
	 * @throws TInvalidDataValueException if the original value is not in the string array.
620
	 */
621
	public static function ensureEnum($value,$enums)
622
	{
623
		static $types=array();
624
		if(func_num_args()===2 && is_string($enums))
625
		{
626
			if(!isset($types[$enums]))
627
				$types[$enums]=new ReflectionClass($enums);
628
			if($types[$enums]->hasConstant($value))
629
				return $value;
630
			else
631
				throw new TInvalidDataValueException(
632
					'propertyvalue_enumvalue_invalid',$value,
633
						implode(' | ',$types[$enums]->getConstants()));
634
		}
635
		else if(!is_array($enums))
636
		{
637
			$enums=func_get_args();
638
			array_shift($enums);
639
		}
640
		if(in_array($value,$enums,true))
641
			return $value;
642
		else
643
			throw new TInvalidDataValueException('propertyvalue_enumvalue_invalid',$value,implode(' | ',$enums));
644
	}
645
}
646
 
647
/**
648
 * TEventParameter class.
649
 * TEventParameter is the base class for all event parameter classes.
650
 *
651
 * @author Qiang Xue <qiang.xue@gmail.com>
652
 * @version $Id: TComponent.php 2541 2008-10-21 15:05:13Z qiang.xue $
653
 * @package System
654
 * @since 3.0
655
 */
656
class TEventParameter extends TComponent
657
{
658
}
659
 
660
/**
661
 * TComponentReflection class.
662
 *
663
 * TComponentReflection provides functionalities to inspect the public/protected
664
 * properties, events and methods defined in a class.
665
 *
666
 * The following code displays the properties and events defined in {@link TDataGrid},
667
 * <code>
668
 *   $reflection=new TComponentReflection('TDataGrid');
669
 *   Prado::varDump($reflection->getProperties());
670
 *   Prado::varDump($reflection->getEvents());
671
 * </code>
672
 *
673
 * @author Qiang Xue <qiang.xue@gmail.com>
674
 * @version $Id: TComponent.php 2541 2008-10-21 15:05:13Z qiang.xue $
675
 * @package System
676
 * @since 3.0
677
 */
678
class TComponentReflection extends TComponent
679
{
680
	private $_className;
681
	private $_properties=array();
682
	private $_events=array();
683
	private $_methods=array();
684
 
685
	/**
686
	 * Constructor.
687
	 * @param object|string the component instance or the class name
688
	 * @throws TInvalidDataTypeException if the object is not a component
689
	 */
690
	public function __construct($component)
691
	{
692
		if(is_string($component) && class_exists($component,false))
693
			$this->_className=$component;
694
		else if(is_object($component))
695
			$this->_className=get_class($component);
696
		else
697
			throw new TInvalidDataTypeException('componentreflection_class_invalid');
698
		$this->reflect();
699
	}
700
 
701
	private function isPropertyMethod($method)
702
	{
703
		$methodName=$method->getName();
704
		return $method->getNumberOfRequiredParameters()===0
705
				&& strncasecmp($methodName,'get',3)===0
706
				&& isset($methodName[3]);
707
	}
708
 
709
	private function isEventMethod($method)
710
	{
711
		$methodName=$method->getName();
712
		return strncasecmp($methodName,'on',2)===0
713
				&& isset($methodName[2]);
714
	}
715
 
716
	private function reflect()
717
	{
718
		$class=new TReflectionClass($this->_className);
719
		$properties=array();
720
		$events=array();
721
		$methods=array();
722
		$isComponent=is_subclass_of($this->_className,'TComponent') || strcasecmp($this->_className,'TComponent')===0;
723
		foreach($class->getMethods() as $method)
724
		{
725
			if($method->isPublic() || $method->isProtected())
726
			{
727
				$methodName=$method->getName();
728
				if(!$method->isStatic() && $isComponent)
729
				{
730
					if($this->isPropertyMethod($method))
731
						$properties[substr($methodName,3)]=$method;
732
					else if($this->isEventMethod($method))
733
					{
734
						$methodName[0]='O';
735
						$events[$methodName]=$method;
736
					}
737
				}
738
				if(strncmp($methodName,'__',2)!==0)
739
					$methods[$methodName]=$method;
740
			}
741
		}
742
		$reserved=array();
743
		ksort($properties);
744
		foreach($properties as $name=>$method)
745
		{
746
			$this->_properties[$name]=array(
747
				'type'=>$this->determinePropertyType($method),
748
				'readonly'=>!$class->hasMethod('set'.$name),
749
				'protected'=>$method->isProtected(),
750
				'class'=>$method->getDeclaringClass()->getName(),
751
				'comments'=>$method->getDocComment()
752
			);
753
			$reserved['get'.strtolower($name)]=1;
754
			$reserved['set'.strtolower($name)]=1;
755
		}
756
		ksort($events);
757
		foreach($events as $name=>$method)
758
		{
759
			$this->_events[$name]=array(
760
				'class'=>$method->getDeclaringClass()->getName(),
761
				'protected'=>$method->isProtected(),
762
				'comments'=>$method->getDocComment()
763
			);
764
			$reserved[strtolower($name)]=1;
765
		}
766
		ksort($methods);
767
		foreach($methods as $name=>$method)
768
		{
769
			if(!isset($reserved[strtolower($name)]))
770
				$this->_methods[$name]=array(
771
					'class'=>$method->getDeclaringClass()->getName(),
772
					'protected'=>$method->isProtected(),
773
					'static'=>$method->isStatic(),
774
					'comments'=>$method->getDocComment()
775
				);
776
		}
777
	}
778
 
779
	/**
780
	 * Determines the property type.
781
	 * This method uses the doc comment to determine the property type.
782
	 * @param ReflectionMethod
783
	 * @return string the property type, '{unknown}' if type cannot be determined from comment
784
	 */
785
	protected function determinePropertyType($method)
786
	{
787
		$comment=$method->getDocComment();
788
		if(preg_match('/@return\\s+(.*?)\\s+/',$comment,$matches))
789
			return $matches[1];
790
		else
791
			return '{unknown}';
792
	}
793
 
794
	/**
795
	 * @return string class name of the component
796
	 */
797
	public function getClassName()
798
	{
799
		return $this->_className;
800
	}
801
 
802
	/**
803
	 * @return array list of component properties. Array keys are property names.
804
	 * Each array element is of the following structure:
805
	 * [type]=>property type,
806
	 * [readonly]=>whether the property is read-only,
807
	 * [protected]=>whether the method is protected or not
808
	 * [class]=>the class where the property is inherited from,
809
	 * [comments]=>comments	associated with the property.
810
	 */
811
	public function getProperties()
812
	{
813
		return $this->_properties;
814
	}
815
 
816
	/**
817
	 * @return array list of component events. Array keys are event names.
818
	 * Each array element is of the following structure:
819
	 * [protected]=>whether the event is protected or not
820
	 * [class]=>the class where the event is inherited from.
821
	 * [comments]=>comments associated with the event.
822
	 */
823
	public function getEvents()
824
	{
825
		return $this->_events;
826
	}
827
 
828
	/**
829
	 * @return array list of public/protected methods. Array keys are method names.
830
	 * Each array element is of the following structure:
831
	 * [protected]=>whether the method is protected or not
832
	 * [static]=>whether the method is static or not
833
	 * [class]=>the class where the property is inherited from,
834
	 * [comments]=>comments associated with the event.
835
	 */
836
	public function getMethods()
837
	{
838
		return $this->_methods;
839
	}
840
}
841