woocommerce/apigen/libs/TokenReflection/TokenReflection/ReflectionProperty.php

573 lines
15 KiB
PHP

<?php
/**
* PHP Token Reflection
*
* Version 1.3.1
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this library in the file LICENSE.
*
* @author Ondřej Nešpor
* @author Jaroslav Hanslík
*/
namespace TokenReflection;
use TokenReflection\Exception, TokenReflection\Stream\StreamBase as Stream;
use ReflectionProperty as InternalReflectionProperty, ReflectionClass as InternalReflectionClass;
/**
* Tokenized class property reflection.
*/
class ReflectionProperty extends ReflectionElement implements IReflectionProperty
{
/**
* Access level of this property has changed from the original implementation.
*
* @see http://svn.php.net/viewvc/php/php-src/branches/PHP_5_3/Zend/zend_compile.h?revision=306939&view=markup#l134
* ZEND_ACC_CHANGED
*
* @var integer
*/
const ACCESS_LEVEL_CHANGED = 0x800;
/**
* Name of the declaring class.
*
* @var string
*/
private $declaringClassName;
/**
* Property modifiers.
*
* @var integer
*/
private $modifiers = 0;
/**
* Determines if modifiers are complete.
*
* @var boolean
*/
private $modifiersComplete = false;
/**
* Property default value.
*
* @var mixed
*/
private $defaultValue;
/**
* Property default value definition (part of the source code).
*
* @var array|string
*/
private $defaultValueDefinition = array();
/**
* Determined if the property value is accessible.
*
* @var boolean
*/
private $accessible = false;
/**
* Declaring trait name.
*
* @var string
*/
private $declaringTraitName;
/**
* Returns a reflection of the declaring class.
*
* @return \TokenReflection\ReflectionClass
*/
public function getDeclaringClass()
{
return $this->getBroker()->getClass($this->declaringClassName);
}
/**
* Returns the name of the declaring class.
*
* @return string
*/
public function getDeclaringClassName()
{
return $this->declaringClassName;
}
/**
* Returns the property default value.
*
* @return mixed
*/
public function getDefaultValue()
{
if (is_array($this->defaultValueDefinition)) {
$this->defaultValue = Resolver::getValueDefinition($this->defaultValueDefinition, $this);
$this->defaultValueDefinition = Resolver::getSourceCode($this->defaultValueDefinition);
}
return $this->defaultValue;
}
/**
* Returns the part of the source code defining the property default value.
*
* @return string
*/
public function getDefaultValueDefinition()
{
return is_array($this->defaultValueDefinition) ? Resolver::getSourceCode($this->defaultValueDefinition) : $this->defaultValueDefinition;
}
/**
* Returns the property value for a particular class instance.
*
* @param object $object
* @return mixed
* @throws \TokenReflection\Exception\RuntimeException If it is not possible to return the property value.
*/
public function getValue($object)
{
$declaringClass = $this->getDeclaringClass();
if (!$declaringClass->isInstance($object)) {
throw new Exception\RuntimeException('The given class is not an instance or subclass of the current class.', Exception\RuntimeException::INVALID_ARGUMENT, $this);
}
if ($this->isPublic()) {
return $object->{$this->name};
} elseif ($this->isAccessible()) {
$refClass = new InternalReflectionClass($object);
$refProperty = $refClass->getProperty($this->name);
$refProperty->setAccessible(true);
$value = $refProperty->getValue($object);
$refProperty->setAccessible(false);
return $value;
}
throw new Exception\RuntimeException('Only public and accessible properties can return their values.', Exception\RuntimeException::NOT_ACCESSBILE, $this);
}
/**
* Returns if the property was created at compile time.
*
* All properties in the source code are.
*
* @return boolean
*/
public function isDefault()
{
return true;
}
/**
* Returns property modifiers.
*
* @return integer
*/
public function getModifiers()
{
if (false === $this->modifiersComplete) {
$declaringClass = $this->getDeclaringClass();
$declaringClassParent = $declaringClass->getParentClass();
if ($declaringClassParent && $declaringClassParent->hasProperty($this->name)) {
$property = $declaringClassParent->getProperty($this->name);
if (($this->isPublic() && !$property->isPublic()) || ($this->isProtected() && $property->isPrivate())) {
$this->modifiers |= self::ACCESS_LEVEL_CHANGED;
}
}
$this->modifiersComplete = ($this->modifiers & self::ACCESS_LEVEL_CHANGED) || $declaringClass->isComplete();
}
return $this->modifiers;
}
/**
* Returns if the property is private.
*
* @return boolean
*/
public function isPrivate()
{
return (bool) ($this->modifiers & InternalReflectionProperty::IS_PRIVATE);
}
/**
* Returns if the property is protected.
*
* @return boolean
*/
public function isProtected()
{
return (bool) ($this->modifiers & InternalReflectionProperty::IS_PROTECTED);
}
/**
* Returns if the property is public.
*
* @return boolean
*/
public function isPublic()
{
return (bool) ($this->modifiers & InternalReflectionProperty::IS_PUBLIC);
}
/**
* Returns if the poperty is static.
*
* @return boolean
*/
public function isStatic()
{
return (bool) ($this->modifiers & InternalReflectionProperty::IS_STATIC);
}
/**
* Returns the string representation of the reflection object.
*
* @return string
*/
public function __toString()
{
return sprintf(
"Property [ %s%s%s%s%s\$%s ]\n",
$this->isStatic() ? '' : '<default> ',
$this->isPublic() ? 'public ' : '',
$this->isPrivate() ? 'private ' : '',
$this->isProtected() ? 'protected ' : '',
$this->isStatic() ? 'static ' : '',
$this->getName()
);
}
/**
* Exports a reflected object.
*
* @param \TokenReflection\Broker $broker Broker instance
* @param string|object $class Class name or class instance
* @param string $property Property name
* @param boolean $return Return the export instead of outputting it
* @return string|null
* @throws \TokenReflection\Exception\RuntimeException If requested parameter doesn't exist.
*/
public static function export(Broker $broker, $class, $property, $return = false)
{
$className = is_object($class) ? get_class($class) : $class;
$propertyName = $property;
$class = $broker->getClass($className);
if ($class instanceof Invalid\ReflectionClass) {
throw new Exception\RuntimeException('Class is invalid.', Exception\RuntimeException::UNSUPPORTED);
} elseif ($class instanceof Dummy\ReflectionClass) {
throw new Exception\RuntimeException(sprintf('Class %s does not exist.', $className), Exception\RuntimeException::DOES_NOT_EXIST);
}
$property = $class->getProperty($propertyName);
if ($return) {
return $property->__toString();
}
echo $property->__toString();
}
/**
* Returns if the property is set accessible.
*
* @return boolean
*/
public function isAccessible()
{
return $this->accessible;
}
/**
* Sets a property to be accessible or not.
*
* @param boolean $accessible If the property should be accessible.
*/
public function setAccessible($accessible)
{
$this->accessible = (bool) $accessible;
}
/**
* Sets the property default value.
*
* @param mixed $value
*/
public function setDefaultValue($value)
{
$this->defaultValue = $value;
$this->defaultValueDefinition = var_export($value, true);
}
/**
* Sets value of a property for a particular class instance.
*
* @param object $object Class instance
* @param mixed $value Poperty value
* @throws \TokenReflection\Exception\RuntimeException If it is not possible to set the property value.
*/
public function setValue($object, $value)
{
$declaringClass = $this->getDeclaringClass();
if (!$declaringClass->isInstance($object)) {
throw new Exception\RuntimeException('Instance of or subclass expected.', Exception\RuntimeException::INVALID_ARGUMENT, $this);
}
if ($this->isPublic()) {
$object->{$this->name} = $value;
} elseif ($this->isAccessible()) {
$refClass = new InternalReflectionClass($object);
$refProperty = $refClass->getProperty($this->name);
$refProperty->setAccessible(true);
$refProperty->setValue($object, $value);
$refProperty->setAccessible(false);
if ($this->isStatic()) {
$this->setDefaultValue($value);
}
} else {
throw new Exception\RuntimeException('Only public and accessible properties can be set.', Exception\RuntimeException::NOT_ACCESSBILE, $this);
}
}
/**
* Returns imported namespaces and aliases from the declaring namespace.
*
* @return array
*/
public function getNamespaceAliases()
{
return $this->getDeclaringClass()->getNamespaceAliases();
}
/**
* Creates a property alias for the given class.
*
* @param \TokenReflection\ReflectionClass $parent New parent class
* @return \TokenReflection\ReflectionProperty
*/
public function alias(ReflectionClass $parent)
{
$property = clone $this;
$property->declaringClassName = $parent->getName();
return $property;
}
/**
* Returns the defining trait.
*
* @return \TokenReflection\IReflectionClass|null
*/
public function getDeclaringTrait()
{
return null === $this->declaringTraitName ? null : $this->getBroker()->getClass($this->declaringTraitName);
}
/**
* Returns the declaring trait name.
*
* @return string|null
*/
public function getDeclaringTraitName()
{
return $this->declaringTraitName;
}
/**
* Returns an element pretty (docblock compatible) name.
*
* @return string
*/
public function getPrettyName()
{
return sprintf('%s::$%s', $this->declaringClassName ?: $this->declaringTraitName, $this->name);
}
/**
* Processes the parent reflection object.
*
* @param \TokenReflection\IReflection $parent Parent reflection object
* @param \TokenReflection\Stream\StreamBase $tokenStream Token substream
* @return \TokenReflection\ReflectionElement
* @throws \TokenReflection\Exception\Parse If an invalid parent reflection object was provided.
*/
protected function processParent(IReflection $parent, Stream $tokenStream)
{
if (!$parent instanceof ReflectionClass) {
throw new Exception\ParseException($this, $tokenStream, 'The parent object has to be an instance of TokenReflection\ReflectionClass.', Exception\ParseException::INVALID_PARENT);
}
$this->declaringClassName = $parent->getName();
if ($parent->isTrait()) {
$this->declaringTraitName = $parent->getName();
}
return parent::processParent($parent, $tokenStream);
}
/**
* Parses reflected element metadata from the token stream.
*
* @param \TokenReflection\Stream\StreamBase $tokenStream Token substream
* @param \TokenReflection\IReflection $parent Parent reflection object
* @return \TokenReflection\ReflectionProperty
*/
protected function parse(Stream $tokenStream, IReflection $parent)
{
$this->parseModifiers($tokenStream, $parent);
if (false === $this->docComment->getDocComment()) {
$this->parseDocComment($tokenStream, $parent);
}
return $this->parseName($tokenStream)
->parseDefaultValue($tokenStream);
}
/**
* Parses class modifiers (abstract, final) and class type (class, interface).
*
* @param \TokenReflection\Stream\StreamBase $tokenStream Token substream
* @param \TokenReflection\ReflectionClass $class Defining class
* @return \TokenReflection\ReflectionClass
* @throws \TokenReflection\Exception\ParseException If the modifiers value cannot be determined.
*/
private function parseModifiers(Stream $tokenStream, ReflectionClass $class)
{
while (true) {
switch ($tokenStream->getType()) {
case T_PUBLIC:
case T_VAR:
$this->modifiers |= InternalReflectionProperty::IS_PUBLIC;
break;
case T_PROTECTED:
$this->modifiers |= InternalReflectionProperty::IS_PROTECTED;
break;
case T_PRIVATE:
$this->modifiers |= InternalReflectionProperty::IS_PRIVATE;
break;
case T_STATIC:
$this->modifiers |= InternalReflectionProperty::IS_STATIC;
break;
default:
break 2;
}
$tokenStream->skipWhitespaces(true);
}
if (InternalReflectionProperty::IS_STATIC === $this->modifiers) {
$this->modifiers |= InternalReflectionProperty::IS_PUBLIC;
} elseif (0 === $this->modifiers) {
$parentProperties = $class->getOwnProperties();
if (empty($parentProperties)) {
throw new Exception\ParseException($this, $tokenStream, 'No access level defined and no previous defining class property present.', Exception\ParseException::LOGICAL_ERROR);
}
$sibling = array_pop($parentProperties);
if ($sibling->isPublic()) {
$this->modifiers = InternalReflectionProperty::IS_PUBLIC;
} elseif ($sibling->isPrivate()) {
$this->modifiers = InternalReflectionProperty::IS_PRIVATE;
} elseif ($sibling->isProtected()) {
$this->modifiers = InternalReflectionProperty::IS_PROTECTED;
} else {
throw new Exception\ParseException($this, $tokenStream, sprintf('Property sibling "%s" has no access level defined.', $sibling->getName()), Exception\Parse::PARSE_ELEMENT_ERROR);
}
if ($sibling->isStatic()) {
$this->modifiers |= InternalReflectionProperty::IS_STATIC;
}
}
return $this;
}
/**
* Parses the property name.
*
* @param \TokenReflection\Stream\StreamBase $tokenStream Token substream
* @return \TokenReflection\ReflectionProperty
* @throws \TokenReflection\Exception\ParseException If the property name could not be determined.
*/
protected function parseName(Stream $tokenStream)
{
if (!$tokenStream->is(T_VARIABLE)) {
throw new Exception\ParseException($this, $tokenStream, 'The property name could not be determined.', Exception\ParseException::LOGICAL_ERROR);
}
$this->name = substr($tokenStream->getTokenValue(), 1);
$tokenStream->skipWhitespaces(true);
return $this;
}
/**
* Parses the propety default value.
*
* @param \TokenReflection\Stream\StreamBase $tokenStream Token substream
* @return \TokenReflection\ReflectionProperty
* @throws \TokenReflection\Exception\ParseException If the property default value could not be determined.
*/
private function parseDefaultValue(Stream $tokenStream)
{
$type = $tokenStream->getType();
if (';' === $type || ',' === $type) {
// No default value
return $this;
}
if ('=' === $type) {
$tokenStream->skipWhitespaces(true);
}
$level = 0;
while (null !== ($type = $tokenStream->getType())) {
switch ($type) {
case ',':
if (0 !== $level) {
break;
}
case ';':
break 2;
case ')':
case ']':
case '}':
$level--;
break;
case '(':
case '{':
case '[':
$level++;
break;
default:
break;
}
$this->defaultValueDefinition[] = $tokenStream->current();
$tokenStream->next();
}
if (',' !== $type && ';' !== $type) {
throw new Exception\ParseException($this, $tokenStream, 'The property default value is not terminated properly. Expected "," or ";".', Exception\ParseException::UNEXPECTED_TOKEN);
}
return $this;
}
}