Namespaces

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

Classes

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