1<?php 2/** 3 * This file is part of phpDocumentor. 4 * 5 * For the full copyright and license information, please view the LICENSE 6 * file that was distributed with this source code. 7 * 8 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org> 9 * @license http://www.opensource.org/licenses/mit-license.php MIT 10 * @link http://phpdoc.org 11 */ 12 13namespace phpDocumentor\Reflection\Types; 14 15/** 16 * Convenience class to create a Context for DocBlocks when not using the Reflection Component of phpDocumentor. 17 * 18 * For a DocBlock to be able to resolve types that use partial namespace names or rely on namespace imports we need to 19 * provide a bit of context so that the DocBlock can read that and based on it decide how to resolve the types to 20 * Fully Qualified names. 21 * 22 * @see Context for more information. 23 */ 24final class ContextFactory 25{ 26 /** The literal used at the end of a use statement. */ 27 const T_LITERAL_END_OF_USE = ';'; 28 29 /** The literal used between sets of use statements */ 30 const T_LITERAL_USE_SEPARATOR = ','; 31 32 /** 33 * Build a Context given a Class Reflection. 34 * 35 * @param \Reflector $reflector 36 * 37 * @see Context for more information on Contexts. 38 * 39 * @return Context 40 */ 41 public function createFromReflector(\Reflector $reflector) 42 { 43 if (method_exists($reflector, 'getDeclaringClass')) { 44 $reflector = $reflector->getDeclaringClass(); 45 } 46 47 $fileName = $reflector->getFileName(); 48 $namespace = $reflector->getNamespaceName(); 49 50 if (file_exists($fileName)) { 51 return $this->createForNamespace($namespace, file_get_contents($fileName)); 52 } 53 54 return new Context($namespace, []); 55 } 56 57 /** 58 * Build a Context for a namespace in the provided file contents. 59 * 60 * @param string $namespace It does not matter if a `\` precedes the namespace name, this method first normalizes. 61 * @param string $fileContents the file's contents to retrieve the aliases from with the given namespace. 62 * 63 * @see Context for more information on Contexts. 64 * 65 * @return Context 66 */ 67 public function createForNamespace($namespace, $fileContents) 68 { 69 $namespace = trim($namespace, '\\'); 70 $useStatements = []; 71 $currentNamespace = ''; 72 $tokens = new \ArrayIterator(token_get_all($fileContents)); 73 74 while ($tokens->valid()) { 75 switch ($tokens->current()[0]) { 76 case T_NAMESPACE: 77 $currentNamespace = $this->parseNamespace($tokens); 78 break; 79 case T_CLASS: 80 // Fast-forward the iterator through the class so that any 81 // T_USE tokens found within are skipped - these are not 82 // valid namespace use statements so should be ignored. 83 $braceLevel = 0; 84 $firstBraceFound = false; 85 while ($tokens->valid() && ($braceLevel > 0 || !$firstBraceFound)) { 86 if ($tokens->current() === '{' 87 || $tokens->current()[0] === T_CURLY_OPEN 88 || $tokens->current()[0] === T_DOLLAR_OPEN_CURLY_BRACES) { 89 if (!$firstBraceFound) { 90 $firstBraceFound = true; 91 } 92 $braceLevel++; 93 } 94 95 if ($tokens->current() === '}') { 96 $braceLevel--; 97 } 98 $tokens->next(); 99 } 100 break; 101 case T_USE: 102 if ($currentNamespace === $namespace) { 103 $useStatements = array_merge($useStatements, $this->parseUseStatement($tokens)); 104 } 105 break; 106 } 107 $tokens->next(); 108 } 109 110 return new Context($namespace, $useStatements); 111 } 112 113 /** 114 * Deduce the name from tokens when we are at the T_NAMESPACE token. 115 * 116 * @param \ArrayIterator $tokens 117 * 118 * @return string 119 */ 120 private function parseNamespace(\ArrayIterator $tokens) 121 { 122 // skip to the first string or namespace separator 123 $this->skipToNextStringOrNamespaceSeparator($tokens); 124 125 $name = ''; 126 while ($tokens->valid() && ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR) 127 ) { 128 $name .= $tokens->current()[1]; 129 $tokens->next(); 130 } 131 132 return $name; 133 } 134 135 /** 136 * Deduce the names of all imports when we are at the T_USE token. 137 * 138 * @param \ArrayIterator $tokens 139 * 140 * @return string[] 141 */ 142 private function parseUseStatement(\ArrayIterator $tokens) 143 { 144 $uses = []; 145 $continue = true; 146 147 while ($continue) { 148 $this->skipToNextStringOrNamespaceSeparator($tokens); 149 150 list($alias, $fqnn) = $this->extractUseStatement($tokens); 151 $uses[$alias] = $fqnn; 152 if ($tokens->current()[0] === self::T_LITERAL_END_OF_USE) { 153 $continue = false; 154 } 155 } 156 157 return $uses; 158 } 159 160 /** 161 * Fast-forwards the iterator as longs as we don't encounter a T_STRING or T_NS_SEPARATOR token. 162 * 163 * @param \ArrayIterator $tokens 164 * 165 * @return void 166 */ 167 private function skipToNextStringOrNamespaceSeparator(\ArrayIterator $tokens) 168 { 169 while ($tokens->valid() && ($tokens->current()[0] !== T_STRING) && ($tokens->current()[0] !== T_NS_SEPARATOR)) { 170 $tokens->next(); 171 } 172 } 173 174 /** 175 * Deduce the namespace name and alias of an import when we are at the T_USE token or have not reached the end of 176 * a USE statement yet. 177 * 178 * @param \ArrayIterator $tokens 179 * 180 * @return string 181 */ 182 private function extractUseStatement(\ArrayIterator $tokens) 183 { 184 $result = ['']; 185 while ($tokens->valid() 186 && ($tokens->current()[0] !== self::T_LITERAL_USE_SEPARATOR) 187 && ($tokens->current()[0] !== self::T_LITERAL_END_OF_USE) 188 ) { 189 if ($tokens->current()[0] === T_AS) { 190 $result[] = ''; 191 } 192 if ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR) { 193 $result[count($result) - 1] .= $tokens->current()[1]; 194 } 195 $tokens->next(); 196 } 197 198 if (count($result) == 1) { 199 $backslashPos = strrpos($result[0], '\\'); 200 201 if (false !== $backslashPos) { 202 $result[] = substr($result[0], $backslashPos + 1); 203 } else { 204 $result[] = $result[0]; 205 } 206 } 207 208 return array_reverse($result); 209 } 210} 211