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
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
      • Traits
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

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

Interfaces

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