7 * @author Mike van Riel <mike.vanriel@naenius.com>
8 * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
9 * @license http://www.opensource.org/licenses/mit-license.php MIT
10 * @link http://phpdoc.org
13 namespace phpDocumentor\Reflection\DocBlock\Type;
15 use phpDocumentor\Reflection\DocBlock\Context;
20 * @author Mike van Riel <mike.vanriel@naenius.com>
21 * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
22 * @license http://www.opensource.org/licenses/mit-license.php MIT
23 * @link http://phpdoc.org
25 class Collection extends \ArrayObject
27 /** @var string Definition of the OR operator for types */
28 const OPERATOR_OR = '|';
30 /** @var string Definition of the ARRAY operator for types */
31 const OPERATOR_ARRAY = '[]';
33 /** @var string Definition of the NAMESPACE operator in PHP */
34 const OPERATOR_NAMESPACE = '\\';
36 /** @var string[] List of recognized keywords */
37 protected static $keywords = array(
38 'string', 'int', 'integer', 'bool', 'boolean', 'float', 'double',
39 'object', 'mixed', 'array', 'resource', 'void', 'null', 'scalar',
40 'callback', 'callable', 'false', 'true', 'self', '$this', 'static'
44 * Current invoking location.
46 * This is used to prepend to type with a relative location.
47 * May also be 'default' or 'global', in which case they are ignored.
51 protected $context = null;
54 * Registers the namespace and aliases; uses that to add and expand the
57 * @param string[] $types Array containing a list of types to add to this
59 * @param Context $location The current invoking location.
61 public function __construct(
62 array $types = array(),
63 Context $context = null
65 $this->context = null === $context ? new Context() : $context;
67 foreach ($types as $type) {
73 * Returns the current invoking location.
77 public function getContext()
79 return $this->context;
83 * Adds a new type to the collection and expands it if it contains a
86 * If a class in the type contains a relative namespace than this collection
87 * will try to expand that into a FQCN.
89 * @param string $type A 'Type' as defined in the phpDocumentor
92 * @throws \InvalidArgumentException if a non-string argument is passed.
94 * @see http://phpdoc.org/docs/latest/for-users/types.html for the
95 * definition of a type.
99 public function add($type)
101 if (!is_string($type)) {
102 throw new \InvalidArgumentException(
103 'A type should be represented by a string, received: '
104 .var_export($type, true)
108 // separate the type by the OR operator
109 $type_parts = explode(self::OPERATOR_OR, $type);
110 foreach ($type_parts as $part) {
111 $expanded_type = $this->expand($part);
112 if ($expanded_type) {
113 $this[] = $expanded_type;
119 * Returns a string representation of the collection.
121 * @return string The resolved types across the collection, separated with
122 * {@link self::OPERATOR_OR}.
124 public function __toString()
126 return implode(self::OPERATOR_OR, $this->getArrayCopy());
130 * Analyzes the given type and returns the FQCN variant.
132 * When a type is provided this method checks whether it is not a keyword or
133 * Fully Qualified Class Name. If so it will use the given namespace and
134 * aliases to expand the type to a FQCN representation.
136 * This method only works as expected if the namespace and aliases are set;
137 * no dynamic reflection is being performed here.
139 * @param string $type The relative or absolute type.
141 * @uses getNamespace to determine with what to prefix the type name.
142 * @uses getNamespaceAliases to check whether the first part of the relative
143 * type name should not be replaced with another namespace.
147 protected function expand($type)
154 if ($this->isTypeAnArray($type)) {
155 return $this->expand(substr($type, 0, -2)) . self::OPERATOR_ARRAY;
158 if ($this->isRelativeType($type) && !$this->isTypeAKeyword($type)) {
159 $type_parts = explode(self::OPERATOR_NAMESPACE, $type, 2);
161 $namespace_aliases = $this->context->getNamespaceAliases();
162 // if the first segment is not an alias; prepend namespace name and
164 if (!isset($namespace_aliases[$type_parts[0]]) &&
165 !isset($namespace_aliases[strstr($type_parts[0], '::', true)])) {
166 $namespace = $this->context->getNamespace();
167 if ('' !== $namespace) {
168 $namespace .= self::OPERATOR_NAMESPACE;
170 return self::OPERATOR_NAMESPACE . $namespace . $type;
173 if (strpos($type_parts[0], '::')) {
174 $type_parts[] = strstr($type_parts[0], '::');
175 $type_parts[0] = $namespace_aliases[strstr($type_parts[0], '::', true)];
176 return implode('', $type_parts);
179 $type_parts[0] = $namespace_aliases[$type_parts[0]];
180 $type = implode(self::OPERATOR_NAMESPACE, $type_parts);
187 * Detects whether the given type represents an array.
189 * @param string $type A relative or absolute type as defined in the
190 * phpDocumentor documentation.
194 protected function isTypeAnArray($type)
196 return substr($type, -2) === self::OPERATOR_ARRAY;
200 * Detects whether the given type represents a PHPDoc keyword.
202 * @param string $type A relative or absolute type as defined in the
203 * phpDocumentor documentation.
207 protected function isTypeAKeyword($type)
209 return in_array(strtolower($type), static::$keywords, true);
213 * Detects whether the given type represents a relative or absolute path.
215 * This method will detect keywords as being absolute; even though they are
216 * not preceeded by a namespace separator.
218 * @param string $type A relative or absolute type as defined in the
219 * phpDocumentor documentation.
223 protected function isRelativeType($type)
225 return ($type[0] !== self::OPERATOR_NAMESPACE)
226 || $this->isTypeAKeyword($type);