[ Index ] |
|
Code source de Symfony 1.0.0 |
1 <?php 2 3 /* 4 * $Id: IntrospectionHelper.php 3076 2006-12-18 08:52:12Z fabien $ 5 * 6 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 7 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 8 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 9 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 10 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 11 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 12 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 13 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 14 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 15 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 16 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 17 * 18 * This software consists of voluntary contributions made by many individuals 19 * and is licensed under the LGPL. For more information please see 20 * <http://phing.info>. 21 */ 22 23 include_once 'phing/types/Reference.php'; 24 include_once 'phing/types/Path.php'; 25 include_once 'phing/util/StringHelper.php'; 26 27 /** 28 * Helper class that collects the methods that a task or nested element 29 * holds to set attributes, create nested elements or hold PCDATA 30 * elements. 31 * 32 *<ul> 33 * <li><strong>SMART-UP INLINE DOCS</strong></li> 34 * <li><strong>POLISH-UP THIS CLASS</strong></li> 35 *</ul> 36 * 37 * @author Andreas Aderhold <andi@binarycloud.com> 38 * @author Hans Lellelid <hans@xmpl.org> 39 * @copyright © 2001,2002 THYRELL. All rights reserved 40 * @version $Revision: 1.19 $ 41 * @package phing 42 */ 43 class IntrospectionHelper { 44 45 46 47 /** 48 * Holds the attribute setter methods. 49 * 50 * @var array string[] 51 */ 52 private $attributeSetters = array(); 53 54 /** 55 * Holds methods to create nested elements. 56 * 57 * @var array string[] 58 */ 59 private $nestedCreators = array(); 60 61 /** 62 * Holds methods to store configured nested elements. 63 * 64 * @var array string[] 65 */ 66 private $nestedStorers = array(); 67 68 /** 69 * Map from attribute names to nested types. 70 */ 71 private $nestedTypes = array(); 72 73 /** 74 * New idea in phing: any class can register certain 75 * keys -- e.g. "task.current_file" -- which can be used in 76 * task attributes, if supported. In the build XML these 77 * are referred to like this: 78 * <regexp pattern="\n" replace="%{task.current_file}"/> 79 * In the type/task a listener method must be defined: 80 * function setListeningReplace($slot) {} 81 * @var array string[] 82 */ 83 private $slotListeners = array(); 84 85 /** 86 * The method to add PCDATA stuff. 87 * 88 * @var string Method name of the addText (redundant?) method, if class supports it :) 89 */ 90 private $methodAddText = null; 91 92 /** 93 * The Class that's been introspected. 94 * 95 * @var object 96 * @access private 97 */ 98 private $bean; 99 100 /** 101 * The cache of IntrospectionHelper classes instantiated by getHelper(). 102 * @var array IntrospectionHelpers[] 103 */ 104 private static $helpers = array(); 105 106 /** 107 * Factory method for helper objects. 108 * 109 * @param string $class The class to create a Helper for 110 */ 111 public static function getHelper($class) { 112 if (!isset(self::$helpers[$class])) { 113 self::$helpers[$class] = new IntrospectionHelper($class); 114 } 115 return self::$helpers[$class]; 116 } 117 118 /** 119 * This function constructs a new introspection helper for a specific class. 120 * 121 * This method loads all methods for the specified class and categorizes them 122 * as setters, creators, slot listeners, etc. This way, the setAttribue() doesn't 123 * need to perform any introspection -- either the requested attribute setter/creator 124 * exists or it does not & a BuildException is thrown. 125 * 126 * @param string $bean The classname for this IH. 127 */ 128 function __construct($class) { 129 130 $this->bean = new ReflectionClass($class); 131 132 //$methods = get_class_methods($bean); 133 foreach($this->bean->getMethods() as $method) { 134 135 if ($method->isPublic()) { 136 137 // We're going to keep case-insensitive method names 138 // for as long as we're allowed :) It makes it much 139 // easier to map XML attributes to PHP class method names. 140 $name = strtolower($method->getName()); 141 142 // There are a few "reserved" names that might look like attribute setters 143 // but should actually just be skipped. (Note: this means you can't ever 144 // have an attribute named "location" or "tasktype" or a nested element named "task".) 145 if ($name === "setlocation" || $name === "settasktype" || $name === "addtask") { 146 continue; 147 } 148 149 if ($name === "addtext") { 150 151 $this->methodAddText = $method; 152 153 } elseif (strpos($name, "setlistening") === 0) { 154 155 // Phing supports something unique called "RegisterSlots" 156 // These are dynamic values that use a basic slot system so that 157 // classes can register to listen to specific slots, and the value 158 // will always be grabbed from the slot (and never set in the project 159 // component). This is useful for things like tracking the current 160 // file being processed by a filter (e.g. AppendTask sets an append.current_file 161 // slot, which can be ready by the XSLTParam type.) 162 163 if (count($method->getParameters()) !== 1) { 164 throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() must take exactly one parameter."); 165 } 166 167 $this->slotListeners[$name] = $method; 168 169 } elseif (strpos($name, "set") === 0) { 170 171 // A standard attribute setter. 172 173 if (count($method->getParameters()) !== 1) { 174 throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() must take exactly one parameter."); 175 } 176 177 $this->attributeSetters[$name] = $method; 178 179 } elseif (strpos($name, "create") === 0) { 180 181 if (count($method->getParameters()) > 0) { 182 throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() may not take any parameters."); 183 } 184 185 // Because PHP doesn't support return types, we are going to do 186 // two things here to guess return type: 187 // 1) parse comments for an explicit value 188 // 2) if that fails, assume that the part of the method after "create" 189 // is the name of the return type (in many cases it is not) 190 191 // This isn't super important -- i.e. we're not instantaiting classes 192 // based on this information. It's more just so that IntrospectionHelper 193 // can keep track of all the nested types -- and provide more helpful 194 // exception messages, etc. 195 196 preg_match('/@return[\s]+([\w]+)/', $method->getDocComment(), $matches); 197 if (!empty($matches[1]) && class_exists($matches[1], false)) { 198 $this->nestedTypes[$name] = $matches[1]; 199 } else { 200 // assume that method createEquals() creates object of type "Equals" 201 // (that example would be false, of course) 202 $this->nestedTypes[$name] = $this->getPropertyName($name, "create"); 203 } 204 205 $this->nestedCreators[$name] = $method; 206 207 } elseif (strpos($name, "addconfigured") === 0) { 208 209 // *must* use class hints if using addConfigured ... 210 211 // 1 param only 212 $params = $method->getParameters(); 213 214 if (count($params) < 1) { 215 throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() must take at least one parameter."); 216 } 217 218 if (count($params) > 1) { 219 $this->warn($method->getDeclaringClass()->getName()."::".$method->getName()."() takes more than one parameter. (IH only uses the first)"); 220 } 221 222 $classname = null; 223 224 if (($hint = $params[0]->getClass()) !== null) { 225 $classname = $hint->getName(); 226 } 227 228 if ($classname === null) { 229 throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() method MUST use a class hint to indicate the class type of parameter."); 230 } 231 232 $this->nestedTypes[$name] = $classname; 233 234 $this->nestedStorers[$name] = $method; 235 236 } elseif (strpos($name, "add") === 0) { 237 238 // *must* use class hints if using add ... 239 240 // 1 param only 241 $params = $method->getParameters(); 242 if (count($params) < 1) { 243 throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() must take at least one parameter."); 244 } 245 246 if (count($params) > 1) { 247 $this->warn($method->getDeclaringClass()->getName()."::".$method->getName()."() takes more than one parameter. (IH only uses the first)"); 248 } 249 250 $classname = null; 251 252 if (($hint = $params[0]->getClass()) !== null) { 253 $classname = $hint->getName(); 254 } 255 256 // we don't use the classname here, but we need to make sure it exists before 257 // we later try to instantiate a non-existant class 258 if ($classname === null) { 259 throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() method MUST use a class hint to indicate the class type of parameter."); 260 } 261 262 $this->nestedCreators[$name] = $method; 263 } 264 } // if $method->isPublic() 265 } // foreach 266 } 267 268 269 /** Sets the named attribute. */ 270 function setAttribute(Project $project, $element, $attributeName, &$value) { 271 272 // we want to check whether the value we are setting looks like 273 // a slot-listener variable: %{task.current_file} 274 // 275 // slot-listener variables are not like properties, in that they cannot be mixed with 276 // other text values. The reason for this disparity is that properties are only 277 // set when first constructing objects from XML, whereas slot-listeners are always dynamic. 278 // 279 // This is made possible by PHP5 (objects automatically passed by reference) and PHP's loose 280 // typing. 281 282 if (StringHelper::isSlotVar($value)) { 283 284 $as = "setlistening" . strtolower($attributeName); 285 286 if (!isset($this->slotListeners[$as])) { 287 $msg = $this->getElementName($project, $element) . " doesn't support a slot-listening '$attributeName' attribute."; 288 throw new BuildException($msg); 289 } 290 291 $method = $this->slotListeners[$as]; 292 293 $key = StringHelper::slotVar($value); 294 $value = Register::getSlot($key); // returns a RegisterSlot object which will hold current value of that register (accessible using getValue()) 295 296 } else { 297 298 // Traditional value options 299 300 $as = "set".strtolower($attributeName); 301 302 if (!isset($this->attributeSetters[$as])) { 303 $msg = $this->getElementName($project, $element) . " doesn't support the '$attributeName' attribute."; 304 throw new BuildException($msg); 305 } 306 307 $method = $this->attributeSetters[$as]; 308 309 if ($as == "setrefid") { 310 $value = new Reference($value); 311 } else { 312 313 // decode any html entities in string 314 $value = html_entity_decode($value); 315 316 // value is a string representation of a boolean type, 317 // convert it to primitive 318 if (StringHelper::isBoolean($value)) { 319 320 $value = StringHelper::booleanValue($value); 321 } 322 323 // does method expect a PhingFile object? if so, then 324 // pass a project-relative file. 325 $params = $method->getParameters(); 326 327 $classname = null; 328 329 if (($hint = $params[0]->getClass()) !== null) { 330 $classname = $hint->getName(); 331 } 332 333 // there should only be one param; we'll just assume .... 334 if ($classname !== null) { 335 switch(strtolower($classname)) { 336 case "phingfile": 337 $value = $project->resolveFile($value); 338 break; 339 case "path": 340 $value = new Path($project, $value); 341 break; 342 case "reference": 343 $value = new Reference($value); 344 break; 345 // any other object params we want to support should go here ... 346 } 347 348 } // if hint !== null 349 350 } // if not setrefid 351 352 } // if is slot-listener 353 354 try { 355 $project->log(" -calling setter ".$method->getDeclaringClass()->getName()."::".$method->getName()."()", PROJECT_MSG_DEBUG); 356 $method->invoke($element, $value); 357 } catch(Exception $exc) { 358 throw new BuildException($exc); 359 } 360 361 } 362 363 /** Adds PCDATA areas.*/ 364 function addText(Project $project, $element, $text) { 365 if ($this->methodAddText === null) { 366 $msg = $this->getElementName($project, $element)." doesn't support nested text data."; 367 throw new BuildException($msg); 368 } 369 try { 370 $method = $this->methodAddText; 371 $method->invoke($element, $text); 372 } catch (Exception $exc) { 373 throw new BuildException($exc); 374 } 375 } 376 377 /** 378 * Creates a named nested element. 379 * 380 * Valid creators can be in the form createFoo() or addFoo(Bar). 381 * @return object Returns the nested element. 382 * @throws BuildException 383 */ 384 function createElement(Project $project, $element, $elementName) { 385 386 $addMethod = "add".strtolower($elementName); 387 $createMethod = "create".strtolower($elementName); 388 $nestedElement = null; 389 390 if (isset($this->nestedCreators[$createMethod])) { 391 392 $method = $this->nestedCreators[$createMethod]; 393 try { // try to invoke the creator method on object 394 $project->log(" -calling creator ".$method->getDeclaringClass()->getName()."::".$method->getName()."()", PROJECT_MSG_DEBUG); 395 $nestedElement = $method->invoke($element); 396 } catch (Exception $exc) { 397 throw new BuildException($exc); 398 } 399 400 } elseif (isset($this->nestedCreators[$addMethod])) { 401 402 $method = $this->nestedCreators[$addMethod]; 403 404 // project components must use class hints to support the add methods 405 406 try { // try to invoke the adder method on object 407 408 $project->log(" -calling adder ".$method->getDeclaringClass()->getName()."::".$method->getName()."()", PROJECT_MSG_DEBUG); 409 // we've already assured that correct num of params 410 // exist and that method is using class hints 411 $params = $method->getParameters(); 412 413 $classname = null; 414 415 if (($hint = $params[0]->getClass()) !== null) { 416 $classname = $hint->getName(); 417 } 418 419 // create a new instance of the object and add it via $addMethod 420 $nestedElement = new $classname(); 421 422 $method->invoke($element, $nestedElement); 423 424 } catch (Exception $exc) { 425 throw new BuildException($exc); 426 } 427 } else { 428 $msg = $this->getElementName($project, $element) . " doesn't support the '$elementName' creator/adder."; 429 throw new BuildException($msg); 430 } 431 432 if ($nestedElement instanceof ProjectComponent) { 433 $nestedElement->setProject($project); 434 } 435 436 return $nestedElement; 437 } 438 439 /** 440 * Creates a named nested element. 441 * @return void 442 * @throws BuildException 443 */ 444 function storeElement($project, $element, $child, $elementName = null) { 445 446 if ($elementName === null) { 447 return; 448 } 449 450 $storer = "addconfigured".strtolower($elementName); 451 452 if (isset($this->nestedStorers[$storer])) { 453 454 $method = $this->nestedStorers[$storer]; 455 456 try { 457 $project->log(" -calling storer ".$method->getDeclaringClass()->getName()."::".$method->getName()."()", PROJECT_MSG_DEBUG); 458 $method->invoke($element, $child); 459 } catch (Exception $exc) { 460 throw new BuildException($exc); 461 } 462 } 463 464 } 465 466 /** Does the introspected class support PCDATA? */ 467 function supportsCharacters() { 468 return ($this->methodAddText !== null); 469 } 470 471 /** Return all attribues supported by the introspected class. */ 472 function getAttributes() { 473 $attribs = array(); 474 foreach (array_keys($this->attributeSetters) as $setter) { 475 $attribs[] =$this->getPropertyName($setter, "set"); 476 } 477 return $attribs; 478 } 479 480 /** Return all nested elements supported by the introspected class. */ 481 function getNestedElements() { 482 return $this->nestedTypes; 483 } 484 485 /** 486 * Get the the name for an element. 487 * When possible the full classnam (phing.tasks.system.PropertyTask) will 488 * be returned. If not available (loaded in taskdefs or typedefs) then the 489 * XML element name will be returned. 490 * 491 * @param Project $project 492 * @param object $element The Task or type element. 493 * @return string Fully qualified class name of element when possible. 494 */ 495 function getElementName(Project $project, $element) { 496 497 $taskdefs = $project->getTaskDefinitions(); 498 $typedefs = $project->getDataTypeDefinitions(); 499 500 // check if class of element is registered with project (tasks & types) 501 // most element types don't have a getTag() method 502 $elClass = get_class($element); 503 504 if (!in_array('getTag', get_class_methods($elClass))) { 505 // loop through taskdefs and typesdefs and see if the class name 506 // matches (case-insensitive) any of the classes in there 507 foreach(array_merge($taskdefs, $typedefs) as $elName => $class) { 508 if (0 === strcasecmp($elClass, StringHelper::unqualify($class))) { 509 return $class; 510 } 511 } 512 return "$elClass (unknown)"; 513 } else { 514 // ->getTag() method does exist, so use it 515 $elName = $element->getTag(); 516 if (isset($taskdefs[$elName])) { 517 return $taskdefs[$elName]; 518 } elseif (isset($typedefs[$elName])) { 519 520 return $typedefs[$elName]; 521 } else { 522 return "$elName (unknown)"; 523 } 524 } 525 } 526 527 /** extract the name of a property from a method name - subtracting a given prefix. */ 528 function getPropertyName($methodName, $prefix) { 529 $start = strlen($prefix); 530 return strtolower(substr($methodName, $start)); 531 } 532 533 /** 534 * Prints warning message to screen if -debug was used. 535 */ 536 function warn($msg) { 537 if (Phing::getMsgOutputLevel() === PROJECT_MSG_DEBUG) { 538 print("[IntrospectionHelper] " . $msg . "\n"); 539 } 540 } 541 542 }
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Fri Mar 16 22:42:14 2007 | par Balluche grâce à PHPXref 0.7 |