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
    • Reflection
    • Security
    • Utils
  • none
  • Tracy
    • Bridges
      • Nette

Classes

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

Interfaces

  • IAnnotation
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  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) { // auto-expire cache
 76:             if ($file && isset(self::$timestamps[$file]) && self::$timestamps[$file] !== filemtime($file)) {
 77:                 unset(self::$cache[$type]);
 78:             }
 79:             unset(self::$timestamps[$file]);
 80:         }
 81: 
 82:         if (isset(self::$cache[$type][$member])) { // is value cached?
 83:             return self::$cache[$type][$member];
 84:         }
 85: 
 86:         if (self::$useReflection === NULL) { // detects whether is reflection available
 87:             self::$useReflection = (bool) ClassType::from(__CLASS__)->getDocComment();
 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 (list(, $token) = each($tokens)) {
279:             switch (is_array($token) ? $token[0] : $token) {
280:                 case T_DOC_COMMENT:
281:                     $docComment = $token[1];
282:                     break;
283: 
284:                 case T_NAMESPACE:
285:                     $namespace = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]) . '\\';
286:                     $uses = [];
287:                     break;
288: 
289:                 case T_CLASS:
290:                 case T_INTERFACE:
291:                 case T_TRAIT:
292:                     if ($name = self::fetch($tokens, T_STRING)) {
293:                         $class = $namespace . $name;
294:                         $classLevel = $level + 1;
295:                         $res[$class] = [];
296:                         if ($docComment) {
297:                             $res[$class]['class'] = $docComment;
298:                         }
299:                         if ($uses) {
300:                             $res[$class]['use'] = $uses;
301:                         }
302:                     }
303:                     break;
304: 
305:                 case T_FUNCTION:
306:                     self::fetch($tokens, '&');
307:                     if ($level === $classLevel && $docComment && ($name = self::fetch($tokens, T_STRING))) {
308:                         $res[$class][$name] = $docComment;
309:                     }
310:                     break;
311: 
312:                 case T_VAR:
313:                 case T_PUBLIC:
314:                 case T_PROTECTED:
315:                     self::fetch($tokens, T_STATIC);
316:                     if ($level === $classLevel && $docComment && ($name = self::fetch($tokens, T_VARIABLE))) {
317:                         $res[$class][$name] = $docComment;
318:                     }
319:                     break;
320: 
321:                 case T_USE:
322:                     while (!$class && ($name = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]))) {
323:                         if (self::fetch($tokens, T_AS)) {
324:                             $uses[self::fetch($tokens, T_STRING)] = ltrim($name, '\\');
325:                         } else {
326:                             $tmp = explode('\\', $name);
327:                             $uses[end($tmp)] = $name;
328:                         }
329:                         if (!self::fetch($tokens, ',')) {
330:                             break;
331:                         }
332:                     }
333:                     break;
334: 
335:                 case T_CURLY_OPEN:
336:                 case T_DOLLAR_OPEN_CURLY_BRACES:
337:                 case '{':
338:                     $level++;
339:                     break;
340: 
341:                 case '}':
342:                     if ($level === $classLevel) {
343:                         $class = $classLevel = NULL;
344:                     }
345:                     $level--;
346:                     // break omitted
347:                 case ';':
348:                     $docComment = NULL;
349:             }
350:         }
351: 
352:         return $res;
353:     }
354: 
355: 
356:     private static function fetch(& $tokens, $take)
357:     {
358:         $res = NULL;
359:         while ($token = current($tokens)) {
360:             list($token, $s) = is_array($token) ? $token : [$token, $token];
361:             if (in_array($token, (array) $take, TRUE)) {
362:                 $res .= $s;
363:             } elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], TRUE)) {
364:                 break;
365:             }
366:             next($tokens);
367:         }
368:         return $res;
369:     }
370: 
371: 
372:     /********************* backend ****************d*g**/
373: 
374: 
375:     /**
376:      * @return void
377:      */
378:     public static function setCacheStorage(Nette\Caching\IStorage $storage)
379:     {
380:         self::$cacheStorage = $storage;
381:     }
382: 
383: 
384:     /**
385:      * @return Nette\Caching\IStorage
386:      */
387:     public static function getCacheStorage()
388:     {
389:         if (!self::$cacheStorage) {
390:             self::$cacheStorage = new Nette\Caching\Storages\MemoryStorage();
391:         }
392:         return self::$cacheStorage;
393:     }
394: 
395: 
396:     /**
397:      * @return Nette\Caching\Cache
398:      */
399:     private static function getCache()
400:     {
401:         return new Nette\Caching\Cache(static::getCacheStorage(), 'Nette.Reflection.Annotations');
402:     }
403: 
404: }
405: 
Nette 2.4-20161109 API API documentation generated by ApiGen 2.8.0