Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Utils
  • none
  • Tracy
    • Bridges
      • Nette

Classes

  • Annotation
  • AnnotationsParser
  • ClassType
  • Extension
  • GlobalFunction
  • Helpers
  • Method
  • Parameter
  • Property

Interfaces

  • IAnnotation
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  • Nette homepage
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Nette\Reflection;
  9: 
 10: use Nette;
 11: use Nette\Utils\Strings;
 12: 
 13: 
 14: /**
 15:  * Annotations support for PHP.
 16:  * @Annotation
 17:  */
 18: class AnnotationsParser
 19: {
 20:     /** @internal single & double quoted PHP string */
 21:     const RE_STRING = '\'(?:\\\\.|[^\'\\\\])*\'|"(?:\\\\.|[^"\\\\])*"';
 22: 
 23:     /** @internal identifier */
 24:     const RE_IDENTIFIER = '[_a-zA-Z\x7F-\xFF][_a-zA-Z0-9\x7F-\xFF-\\\]*';
 25: 
 26:     /** @var bool */
 27:     public static $useReflection;
 28: 
 29:     /** @var bool */
 30:     public static $autoRefresh = TRUE;
 31: 
 32:     /** @var array */
 33:     public static $inherited = array('description', 'param', 'return');
 34: 
 35:     /** @var array */
 36:     private static $cache;
 37: 
 38:     /** @var array */
 39:     private static $timestamps;
 40: 
 41:     /** @var Nette\Caching\IStorage */
 42:     private static $cacheStorage;
 43: 
 44: 
 45:     /**
 46:      * Static class - cannot be instantiated.
 47:      */
 48:     final public function __construct()
 49:     {
 50:         throw new Nette\StaticClassException;
 51:     }
 52: 
 53: 
 54:     /**
 55:      * Returns annotations.
 56:      * @param  \ReflectionClass|\ReflectionMethod|\ReflectionProperty
 57:      * @return array
 58:      */
 59:     public static function getAll(\Reflector $r)
 60:     {
 61:         if ($r instanceof \ReflectionClass) {
 62:             $type = $r->getName();
 63:             $member = 'class';
 64:             $file = $r->getFileName();
 65: 
 66:         } elseif ($r instanceof \ReflectionMethod) {
 67:             $type = $r->getDeclaringClass()->getName();
 68:             $member = $r->getName();
 69:             $file = $r->getFileName();
 70: 
 71:         } elseif ($r instanceof \ReflectionFunction) {
 72:             $type = NULL;
 73:             $member = $r->getName();
 74:             $file = $r->getFileName();
 75: 
 76:         } else {
 77:             $type = $r->getDeclaringClass()->getName();
 78:             $member = '$' . $r->getName();
 79:             $file = $r->getDeclaringClass()->getFileName();
 80:         }
 81: 
 82:         if (!self::$useReflection) { // auto-expire cache
 83:             if ($file && isset(self::$timestamps[$file]) && self::$timestamps[$file] !== filemtime($file)) {
 84:                 unset(self::$cache[$type]);
 85:             }
 86:             unset(self::$timestamps[$file]);
 87:         }
 88: 
 89:         if (isset(self::$cache[$type][$member])) { // is value cached?
 90:             return self::$cache[$type][$member];
 91:         }
 92: 
 93:         if (self::$useReflection === NULL) { // detects whether is reflection available
 94:             self::$useReflection = (bool) ClassType::from(__CLASS__)->getDocComment();
 95:         }
 96: 
 97:         if (self::$useReflection) {
 98:             $annotations = self::parseComment($r->getDocComment());
 99: 
100:         } else {
101:             $outerCache = self::getCache();
102: 
103:             if (self::$cache === NULL) {
104:                 self::$cache = (array) $outerCache->load('list');
105:                 self::$timestamps = isset(self::$cache['*']) ? self::$cache['*'] : array();
106:             }
107: 
108:             if (!isset(self::$cache[$type]) && $file) {
109:                 self::$cache['*'][$file] = filemtime($file);
110:                 foreach (static::parsePhp(file_get_contents($file)) as $class => $info) {
111:                     foreach ($info as $prop => $comment) {
112:                         if ($prop !== 'use') {
113:                             self::$cache[$class][$prop] = self::parseComment($comment);
114:                         }
115:                     }
116:                 }
117:                 $outerCache->save('list', self::$cache);
118:             }
119: 
120:             if (isset(self::$cache[$type][$member])) {
121:                 $annotations = self::$cache[$type][$member];
122:             } else {
123:                 $annotations = array();
124:             }
125:         }
126: 
127:         if ($r instanceof \ReflectionMethod && !$r->isPrivate()
128:             && (!$r->isConstructor() || !empty($annotations['inheritdoc'][0]))
129:         ) {
130:             try {
131:                 $inherited = self::getAll(new \ReflectionMethod(get_parent_class($type), $member));
132:             } catch (\ReflectionException $e) {
133:                 try {
134:                     $inherited = self::getAll($r->getPrototype());
135:                 } catch (\ReflectionException $e) {
136:                     $inherited = array();
137:                 }
138:             }
139:             $annotations += array_intersect_key($inherited, array_flip(self::$inherited));
140:         }
141: 
142:         return self::$cache[$type][$member] = $annotations;
143:     }
144: 
145: 
146:     /**
147:      * Expands class name into FQN.
148:      * @param  string
149:      * @return string  fully qualified class name
150:      * @throws Nette\InvalidArgumentException
151:      */
152:     public static function expandClassName($name, \ReflectionClass $reflector)
153:     {
154:         if (empty($name)) {
155:             throw new Nette\InvalidArgumentException('Class name must not be empty.');
156: 
157:         } elseif ($name === 'self') {
158:             return $reflector->getName();
159: 
160:         } elseif ($name[0] === '\\') { // already fully qualified
161:             return ltrim($name, '\\');
162:         }
163: 
164:         $filename = $reflector->getFileName();
165:         $parsed = static::getCache()->load($filename, function (& $dp) use ($filename) {
166:             if (AnnotationsParser::$autoRefresh) {
167:                 $dp[Nette\Caching\Cache::FILES] = $filename;
168:             }
169:             return AnnotationsParser::parsePhp(file_get_contents($filename));
170:         });
171:         $uses = array_change_key_case((array) $tmp = & $parsed[$reflector->getName()]['use']);
172:         $parts = explode('\\', $name, 2);
173:         $parts[0] = strtolower($parts[0]);
174:         if (isset($uses[$parts[0]])) {
175:             $parts[0] = $uses[$parts[0]];
176:             return implode('\\', $parts);
177: 
178:         } elseif ($reflector->inNamespace()) {
179:             return $reflector->getNamespaceName() . '\\' . $name;
180: 
181:         } else {
182:             return $name;
183:         }
184:     }
185: 
186: 
187:     /**
188:      * Parses phpDoc comment.
189:      * @param  string
190:      * @return array
191:      */
192:     private static function parseComment($comment)
193:     {
194:         static $tokens = array('true' => TRUE, 'false' => FALSE, 'null' => NULL, '' => TRUE);
195: 
196:         $res = array();
197:         $comment = preg_replace('#^\s*\*\s?#ms', '', trim($comment, '/*'));
198:         $parts = preg_split('#^\s*(?=@'.self::RE_IDENTIFIER.')#m', $comment, 2);
199: 
200:         $description = trim($parts[0]);
201:         if ($description !== '') {
202:             $res['description'] = array($description);
203:         }
204: 
205:         $matches = Strings::matchAll(
206:             isset($parts[1]) ? $parts[1] : '',
207:             '~
208:                 (?<=\s|^)@('.self::RE_IDENTIFIER.')[ \t]*      ##  annotation
209:                 (
210:                     \((?>'.self::RE_STRING.'|[^\'")@]+)+\)|  ##  (value)
211:                     [^(@\r\n][^@\r\n]*|)                     ##  value
212:             ~xi'
213:         );
214: 
215:         foreach ($matches as $match) {
216:             list(, $name, $value) = $match;
217: 
218:             if (substr($value, 0, 1) === '(') {
219:                 $items = array();
220:                 $key = '';
221:                 $val = TRUE;
222:                 $value[0] = ',';
223:                 while ($m = Strings::match(
224:                     $value,
225:                     '#\s*,\s*(?>(' . self::RE_IDENTIFIER . ')\s*=\s*)?(' . self::RE_STRING . '|[^\'"),\s][^\'"),]*)#A')
226:                 ) {
227:                     $value = substr($value, strlen($m[0]));
228:                     list(, $key, $val) = $m;
229:                     $val = rtrim($val);
230:                     if ($val[0] === "'" || $val[0] === '"') {
231:                         $val = substr($val, 1, -1);
232: 
233:                     } elseif (is_numeric($val)) {
234:                         $val = 1 * $val;
235: 
236:                     } else {
237:                         $lval = strtolower($val);
238:                         $val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val;
239:                     }
240: 
241:                     if ($key === '') {
242:                         $items[] = $val;
243: 
244:                     } else {
245:                         $items[$key] = $val;
246:                     }
247:                 }
248: 
249:                 $value = count($items) < 2 && $key === '' ? $val : $items;
250: 
251:             } else {
252:                 $value = trim($value);
253:                 if (is_numeric($value)) {
254:                     $value = 1 * $value;
255: 
256:                 } else {
257:                     $lval = strtolower($value);
258:                     $value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value;
259:                 }
260:             }
261: 
262:             $res[$name][] = is_array($value) ? Nette\Utils\ArrayHash::from($value) : $value;
263:         }
264: 
265:         return $res;
266:     }
267: 
268: 
269:     /**
270:      * Parses PHP file.
271:      * @param  string
272:      * @return array [class => [prop => comment (or 'use' => [alias => class])]
273:      * @internal
274:      */
275:     public static function parsePhp($code)
276:     {
277:         if (Strings::match($code, '#//nette'.'loader=(\S*)#')) {
278:             return;
279:         }
280: 
281:         $tokens = @token_get_all($code);
282:         $namespace = $class = $classLevel = $level = $docComment = NULL;
283:         $res = $uses = array();
284: 
285:         while (list(, $token) = each($tokens)) {
286:             switch (is_array($token) ? $token[0] : $token) {
287:                 case T_DOC_COMMENT:
288:                     $docComment = $token[1];
289:                     break;
290: 
291:                 case T_NAMESPACE:
292:                     $namespace = self::fetch($tokens, array(T_STRING, T_NS_SEPARATOR)) . '\\';
293:                     $uses = array();
294:                     break;
295: 
296:                 case T_CLASS:
297:                 case T_INTERFACE:
298:                 case PHP_VERSION_ID < 50400 ? -1 : T_TRAIT:
299:                     if ($name = self::fetch($tokens, T_STRING)) {
300:                         $class = $namespace . $name;
301:                         $classLevel = $level + 1;
302:                         $res[$class] = array();
303:                         if ($docComment) {
304:                             $res[$class]['class'] = $docComment;
305:                         }
306:                         if ($uses) {
307:                             $res[$class]['use'] = $uses;
308:                         }
309:                     }
310:                     break;
311: 
312:                 case T_FUNCTION:
313:                     self::fetch($tokens, '&');
314:                     if ($level === $classLevel && $docComment && ($name = self::fetch($tokens, T_STRING))) {
315:                         $res[$class][$name] = $docComment;
316:                     }
317:                     break;
318: 
319:                 case T_VAR:
320:                 case T_PUBLIC:
321:                 case T_PROTECTED:
322:                     self::fetch($tokens, T_STATIC);
323:                     if ($level === $classLevel && $docComment && ($name = self::fetch($tokens, T_VARIABLE))) {
324:                         $res[$class][$name] = $docComment;
325:                     }
326:                     break;
327: 
328:                 case T_USE:
329:                     while (!$class && ($name = self::fetch($tokens, array(T_STRING, T_NS_SEPARATOR)))) {
330:                         if (self::fetch($tokens, T_AS)) {
331:                             $uses[self::fetch($tokens, T_STRING)] = ltrim($name, '\\');
332:                         } else {
333:                             $tmp = explode('\\', $name);
334:                             $uses[end($tmp)] = $name;
335:                         }
336:                         if (!self::fetch($tokens, ',')) {
337:                             break;
338:                         }
339:                     }
340:                     break;
341: 
342:                 case T_CURLY_OPEN:
343:                 case T_DOLLAR_OPEN_CURLY_BRACES:
344:                 case '{':
345:                     $level++;
346:                     break;
347: 
348:                 case '}':
349:                     if ($level === $classLevel) {
350:                         $class = $classLevel = NULL;
351:                     }
352:                     $level--;
353:                     // break omitted
354:                 case ';':
355:                     $docComment = NULL;
356:             }
357:         }
358: 
359:         return $res;
360:     }
361: 
362: 
363:     private static function fetch(& $tokens, $take)
364:     {
365:         $res = NULL;
366:         while ($token = current($tokens)) {
367:             list($token, $s) = is_array($token) ? $token : array($token, $token);
368:             if (in_array($token, (array) $take, TRUE)) {
369:                 $res .= $s;
370:             } elseif (!in_array($token, array(T_DOC_COMMENT, T_WHITESPACE, T_COMMENT), TRUE)) {
371:                 break;
372:             }
373:             next($tokens);
374:         }
375:         return $res;
376:     }
377: 
378: 
379:     /********************* backend ****************d*g**/
380: 
381: 
382:     /**
383:      * @return void
384:      */
385:     public static function setCacheStorage(Nette\Caching\IStorage $storage)
386:     {
387:         self::$cacheStorage = $storage;
388:     }
389: 
390: 
391:     /**
392:      * @return Nette\Caching\IStorage
393:      */
394:     public static function getCacheStorage()
395:     {
396:         if (!self::$cacheStorage) {
397:             self::$cacheStorage = new Nette\Caching\Storages\MemoryStorage();
398:         }
399:         return self::$cacheStorage;
400:     }
401: 
402: 
403:     /**
404:      * @return Nette\Caching\Cache
405:      */
406:     private static function getCache()
407:     {
408:         return new Nette\Caching\Cache(static::getCacheStorage(), 'Nette.Reflection.Annotations');
409:     }
410: 
411: }
412: 
Nette 2.3-20161221 API API documentation generated by ApiGen 2.8.0