1<?php 2/** 3 * This file is part of the static reflection component. 4 * 5 * PHP Version 5 6 * 7 * Copyright (c) 2009-2011, Manuel Pichler <mapi@pdepend.org>. 8 * All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 14 * * Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 17 * * Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in 19 * the documentation and/or other materials provided with the 20 * distribution. 21 * 22 * * Neither the name of Manuel Pichler nor the names of his 23 * contributors may be used to endorse or promote products derived 24 * from this software without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 29 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 30 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 31 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 32 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 33 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 36 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37 * POSSIBILITY OF SUCH DAMAGE. 38 * 39 * @category PHP 40 * @package pdepend\reflection\api 41 * @author Manuel Pichler <mapi@pdepend.org> 42 * @copyright 2009-2011 Manuel Pichler. All rights reserved. 43 * @license http://www.opensource.org/licenses/bsd-license.php BSD License 44 * @version SVN: $Id$ 45 * @link http://pdepend.org/ 46 */ 47 48namespace pdepend\reflection\api; 49 50/** 51 * Static class implementation. 52 * 53 * @category PHP 54 * @package pdepend\reflection\api 55 * @author Manuel Pichler <mapi@pdepend.org> 56 * @copyright 2009-2011 Manuel Pichler. All rights reserved. 57 * @license http://www.opensource.org/licenses/bsd-license.php BSD License 58 * @version Release: @package_version@ 59 * @link http://pdepend.org/ 60 */ 61class StaticReflectionClass extends StaticReflectionInterface 62{ 63 /** 64 * The type of this class. 65 */ 66 const TYPE = __CLASS__; 67 68 /** 69 * Bitfield with class modifiers. 70 * 71 * @var integer 72 */ 73 private $_modifiers = 0; 74 75 /** 76 * Optional parent class. 77 * 78 * @var \ReflectionClass 79 */ 80 private $_parentClass = false; 81 82 /** 83 * Array with properties defined/declared in the context class. 84 * 85 * @var array(string=>\ReflectionProperty) 86 */ 87 private $_properties = null; 88 89 /** 90 * Constructs a new reflection class instance. 91 * 92 * @param string $name The qualified class name. 93 * @param string $docComment Doc comment for this class. 94 * @param integer $modifiers Modifiers for this class. 95 */ 96 public function __construct( $name, $docComment, $modifiers ) 97 { 98 parent::__construct( $name, $docComment ); 99 100 $this->_modifiers = $modifiers; 101 } 102 103 /** 104 * Returns a bitfield with modifiers for the reflected class. 105 * 106 * @return integer 107 */ 108 public function getModifiers() 109 { 110 if ( count( $this->getMethods( StaticReflectionMethod::IS_ABSTRACT ) ) > 0 ) 111 { 112 return $this->_modifiers | self::IS_IMPLICIT_ABSTRACT; 113 } 114 return $this->_modifiers; 115 } 116 117 /** 118 * Returns <b>true</b> when the class is declared abstract or is an interface. 119 * 120 * @return boolean 121 */ 122 public function isAbstract() 123 { 124 return ( $this->_isExplicitAbstract() || $this->_isImplicitAbstract() ); 125 } 126 127 /** 128 * Returns <b>true</b> when the explicit abstract modifier is present. 129 * 130 * @return boolean 131 */ 132 private function _isExplicitAbstract() 133 { 134 return ( ( $this->getModifiers() & self::IS_EXPLICIT_ABSTRACT ) === self::IS_EXPLICIT_ABSTRACT ); 135 } 136 137 /** 138 * Returns <b>true</b> when the implicit abstract modifier is present. 139 * 140 * @return boolean 141 */ 142 private function _isImplicitAbstract() 143 { 144 return ( ( $this->getModifiers() & self::IS_IMPLICIT_ABSTRACT ) === self::IS_IMPLICIT_ABSTRACT ); 145 } 146 147 /** 148 * Returns <b>true</b> when the class is declared as final. 149 * 150 * @return boolean 151 */ 152 public function isFinal() 153 { 154 return ( ( $this->_modifiers & self::IS_FINAL ) === self::IS_FINAL ); 155 } 156 157 /** 158 * Returns <b>true</b> when the reflected class/interface is an interface, 159 * which means that this concrete implementation always returns <b>false</b>. 160 * 161 * @return boolean 162 */ 163 public function isInterface() 164 { 165 return false; 166 } 167 168 /** 169 * Checks that the reflected class is a child of the given class name. 170 * 171 * @param string $class Name of the searched class. 172 * 173 * @return boolean 174 */ 175 public function isSubclassOf( $class ) 176 { 177 if ( $this->_parentClass === false ) 178 { 179 return parent::isSubclassOf( $class ); 180 } 181 else if ( strcasecmp( $this->_parentClass->getName(), ltrim( $class, '\\' ) ) === 0 ) 182 { 183 return true; 184 } 185 return $this->_parentClass->isSubclassOf( $class ); 186 } 187 188 /** 189 * Checks if the class is instantiable. 190 * 191 * @return boolean 192 */ 193 public function isInstantiable() 194 { 195 if ( $this->isAbstract() ) 196 { 197 return false; 198 } 199 else if ( is_object( $constructor = $this->getConstructor() ) ) 200 { 201 return ( $constructor->isPublic() && !$constructor->isAbstract() ); 202 } 203 return true; 204 } 205 206 /** 207 * This method will return <b>true</b> when the reflected object is a class 208 * and implements the interface <b>Traversable</b>. 209 * 210 * @return boolean 211 */ 212 public function isIterateable() 213 { 214 return $this->isSubclassOf( 'Traversable' ); 215 } 216 217 /** 218 * Returns the parent of the reflected class or <b>false</b> when no parent 219 * exists. 220 * 221 * @return \ReflectionClass|boolean 222 */ 223 public function getParentClass() 224 { 225 return $this->_parentClass; 226 } 227 228 /** 229 * Initializes the parent class of the reflected class. 230 * 231 * @param \ReflectionClass $parentClass The parent class instance. 232 * 233 * @return void 234 * @access private 235 * @throws \LogicException When the parentClass property was already set. 236 */ 237 public function initParentClass( \ReflectionClass $parentClass ) 238 { 239 if ( $this->_parentClass === false ) 240 { 241 $this->_parentClass = $parentClass; 242 } 243 else 244 { 245 throw new \LogicException( 'Property parentClass already set' ); 246 } 247 } 248 249 /** 250 * Returns an array with all implemented/extended interfaces. 251 * 252 * @return array(\ReflectionClass) 253 */ 254 public function getInterfaces() 255 { 256 $result = $this->_mergeInterfaces( parent::getInterfaces() ); 257 if ( is_object( $parentClass = $this->getParentClass() ) ) 258 { 259 $result = $this->_mergeInterfaces( $parentClass->getInterfaces(), $result ); 260 } 261 return array_values( $result ); 262 } 263 264 /** 265 * Merges the given interfaces into the given result array. 266 * 267 * @param array(\ReflectionClass) $interfaces Input interface array. 268 * @param array(string=>\ReflectionClass) $result Result interface list. 269 * 270 * @return array(\ReflectionClass) 271 */ 272 private function _mergeInterfaces( array $interfaces, array &$result = array()) 273 { 274 foreach ( $interfaces as $interface ) 275 { 276 if ( !isset( $result[$interface->getName()] ) ) 277 { 278 $result[$interface->getName()] = $interface; 279 } 280 } 281 return $result; 282 } 283 284 /** 285 * Returns an array with constants defined in this or one of its parent 286 * classes. 287 * 288 * @return array(string=>mixed) 289 */ 290 public function getConstants() 291 { 292 if ( $this->_parentClass === false ) 293 { 294 return parent::getConstants(); 295 } 296 return $this->_collectConstants( $this->_parentClass, parent::getConstants() ); 297 } 298 299 /** 300 * Collects all constants from the given class when they are not present in 301 * the given <b>$result</b> array. 302 * 303 * @param \ReflectionClass $class The context class. 304 * @param array(string=>mixed) $result Previous collected constants 305 * 306 * @return array(string=>mixed) 307 */ 308 private function _collectConstants( \ReflectionClass $class, array $result ) 309 { 310 foreach ( $class->getConstants() as $name => $value ) 311 { 312 if ( array_key_exists( $name, $result ) === false ) 313 { 314 $result[$name] = $value; 315 } 316 } 317 return $result; 318 } 319 320 /** 321 * Returns an <b>array</b> with methods defined in the inheritence hierarchy 322 * of the reflection class. You can pass an optional filter argument that 323 * contains a bitfield of required method modifiers. 324 * 325 * @param integer $filter Optional filter for the returned methods 326 * 327 * @return array(\ReflectionMethod) 328 */ 329 public function getMethods( $filter = -1 ) 330 { 331 if ( $this->_parentClass === false ) 332 { 333 return parent::getMethods( $filter ); 334 } 335 return $this->_collectMethodsFromParentClass( $filter ); 336 } 337 338 /** 339 * Will collect all methods from a parent class. 340 * 341 * @param integer $filter Optional bitfield with modifiers that a collected 342 * method must match to. 343 * 344 * @return array(\ReflectionMethod) 345 */ 346 private function _collectMethodsFromParentClass( $filter ) 347 { 348 $result = parent::collectMethods(); 349 foreach ( $this->_parentClass->getMethods() as $method ) 350 { 351 $result = $this->_collectMethodFromParentClass( $method, $result ); 352 } 353 return $this->prepareCollectedObjects( $filter, $result ); 354 } 355 356 /** 357 * Will add the given method to the list of available methods. 358 * 359 * @param \ReflectionMethod $method The currently reflected method 360 * @param array(\ReflectionMethod) $result List with already found methods. 361 * 362 * @return array(\ReflectionMethod) 363 */ 364 private function _collectMethodFromParentClass( \ReflectionMethod $method, array $result ) 365 { 366 $name = strtolower( $method->getName() ); 367 if ( !isset( $result[$name] ) ) 368 { 369 $result[$name] = $method; 370 } 371 else if ( $result[$name]->isAbstract() && !$method->isAbstract() ) 372 { 373 $result[$name] = $method; 374 } 375 return $result; 376 } 377 378 /** 379 * Returns the constructor method of the reflected class or <b>null</b> when 380 * no constructor is available. 381 * 382 * @return \ReflectionMethod 383 */ 384 public function getConstructor() 385 { 386 foreach ( $this->getMethods() as $method ) 387 { 388 if ( $method->isConstructor() ) 389 { 390 return $method; 391 } 392 } 393 return null; 394 } 395 396 /** 397 * Checks whether the specified property is defined. 398 * 399 * @param string $name Name of the property being checked for. 400 * 401 * @return boolean 402 */ 403 public function hasProperty( $name ) 404 { 405 return array_key_exists( $name, $this->_collectProperties() ); 406 } 407 408 /** 409 * Returns a property for the given name or throws an exception when no such 410 * property exists. 411 * 412 * @param string $name Name of the searched property. 413 * 414 * @return \ReflectionProperty 415 */ 416 public function getProperty( $name ) 417 { 418 if ( $this->hasProperty( $name ) ) 419 { 420 $properties = $this->_collectProperties(); 421 return $properties[$name]; 422 } 423 throw new \ReflectionException( sprintf( 'Property %s does not exist', $name ) ); 424 } 425 426 /** 427 * Returns all properties of the reflected class or one of its parent classes. 428 * 429 * @param integer $filter Optional bitfield with property modifiers that 430 * will be used as a filter for the collected properties. 431 * 432 * @return array(\ReflectionProperty) 433 */ 434 public function getProperties( $filter = -1 ) 435 { 436 return $this->prepareCollectedObjects( $filter, $this->_collectProperties() ); 437 } 438 439 /** 440 * Collects all properties defined for the reflected class or one of its 441 * parents. 442 * 443 * @return array(string=>\ReflectionProperty) 444 */ 445 private function _collectProperties() 446 { 447 if ( $this->_parentClass === false ) 448 { 449 return (array) $this->_properties; 450 } 451 return $this->_collectPropertiesFromParentClass( (array) $this->_properties ); 452 } 453 454 /** 455 * Collects all properties of the current an its parent class. 456 * 457 * @param array(string=>\ReflectionProperty) $result Properties defined 458 * within the scope of the currently reflected class. 459 * 460 * @return array(string=>\ReflectionProperty) 461 */ 462 private function _collectPropertiesFromParentClass( array $result ) 463 { 464 foreach ( $this->_parentClass->getProperties() as $property ) 465 { 466 $result = $this->_collectPropertyFromParentClass( $property, $result ); 467 } 468 return $result; 469 } 470 471 /** 472 * Adds the given property to the result array when it does not already 473 * exist and is not declared as private. 474 * 475 * @param ReflectionProperty $property The current property 476 * instance that should be added to the result of available properties. 477 * @param array(string=>\ReflectionProperty) $result An array with all 478 * properties that have already been collected for the reflected class. 479 * 480 * @return array(string=>\ReflectionProperty) 481 */ 482 private function _collectPropertyFromParentClass( \ReflectionProperty $property, array $result ) 483 { 484 if ( !$property->isPrivate() && !isset( $result[$property->getName()] ) ) 485 { 486 $result[$property->getName()] = $property; 487 } 488 return $result; 489 } 490 491 /** 492 * Tries to initializes the properties of the reflected class the first time, 493 * it will throw an exception when the properties are already set . 494 * 495 * @param array(\ReflectionProperty) $properties The properties of this class. 496 * 497 * @return void 498 * @access private 499 */ 500 public function initProperties( array $properties ) 501 { 502 if ( $this->_properties === null ) 503 { 504 $this->_initProperties( $properties ); 505 } 506 else 507 { 508 throw new \LogicException( 'Property properties already set' ); 509 } 510 } 511 512 /** 513 * Initializes the properties of the reflected class. 514 * 515 * @param array(\ReflectionProperty) $properties The properties of this class. 516 * 517 * @return void 518 */ 519 private function _initProperties( array $properties ) 520 { 521 $this->_properties = array(); 522 foreach ( $properties as $property ) 523 { 524 $property->initDeclaringClass( $this ); 525 $this->_properties[$property->getName()] = $property; 526 } 527 } 528 529 /** 530 * Gets the static property values. 531 * 532 * @param string $name The property name. 533 * @param mixed $default Optional default value. 534 * 535 * @return mixed 536 */ 537 public function getStaticPropertyValue( $name, $default = null ) 538 { 539 $properties = $this->getStaticProperties(); 540 if ( array_key_exists( $name, $properties ) ) 541 { 542 return $properties[$name]; 543 } 544 return parent::getStaticPropertyValue( $name, $default ); 545 } 546 547 /** 548 * Get the static properties. 549 * 550 * @return array(string=>mixed) 551 */ 552 public function getStaticProperties() 553 { 554 $properties = array(); 555 foreach ( $this->_collectProperties() as $name => $property ) 556 { 557 if ( $property->isStatic() ) 558 { 559 $properties[$name] = $property->getValue(); 560 } 561 } 562 return $properties; 563 } 564 565 /** 566 * Returns a string representation of the currently reflected class. 567 * 568 * @return string 569 */ 570 public function __toString() 571 { 572 return sprintf( 573 'Class [ <user> %sclass %s%s%s ] %s', 574 $this->isAbstract() ? 'abstract ' : '', 575 $this->getShortName(), 576 $this->parentClassToString(), 577 $this->implementedInterfacesToString(), 578 $this->bodyToString() 579 ); 580 } 581 582 /** 583 * Returns a string representation of the parent class or an empty string 584 * when no parent was declared. 585 * 586 * @return string 587 */ 588 protected function parentClassToString() 589 { 590 if ( $this->getParentClass() === false ) 591 { 592 return ''; 593 } 594 return ' extends ' . $this->getParentClass()->getName(); 595 } 596 597 /** 598 * This method returns a string representation of the parent interfaces or 599 * an empty string when the reflected class does not declare any parents. 600 * 601 * @return string 602 */ 603 protected function implementedInterfacesToString() 604 { 605 if ( count( $this->getInterfaceNames() ) === 0 ) 606 { 607 return ''; 608 } 609 return ' implements ' . join( ', ', $this->getInterfaceNames() ); 610 } 611 612 /** 613 * This method returns a string representation of all properties declared 614 * for the currently reflected interface. 615 * 616 * @return string 617 */ 618 protected function propertiesToString() 619 { 620 $string = ''; 621 foreach ( $this->getProperties() as $property ) 622 { 623 $string .= $property->__toString( ' ' ) . PHP_EOL; 624 } 625 return $string; 626 } 627} 628