Overview

Namespaces

  • Budovy
  • Kdyby
    • BootstrapFormRenderer
      • DI
      • Latte
  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Diagnostics
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
      • Diagnostics
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • PhpGenerator
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
  • NetteModule
  • Nextras
    • Datagrid
  • None
  • PHP
  • Tester
    • CodeCoverage
    • Runner
      • Output
  • Vodacek
    • Forms
      • Controls
  • WebLoader
    • Filter
    • Nette

Classes

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

Interfaces

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