Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Adapters
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • NetteModule
  • none

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:     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 = '';
 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:                 self::$cacheStorage = new Nette\Caching\Storages\DevNullStorage;
 95:             }
 96:             $outerCache = new Nette\Caching\Cache(self::$cacheStorage, 'Nette.Reflection.Annotations');
 97: 
 98:             if (self::$cache === NULL) {
 99:                 self::$cache = (array) $outerCache->offsetGet('list');
100:                 self::$timestamps = isset(self::$cache['*']) ? self::$cache['*'] : array();
101:             }
102: 
103:             if (!isset(self::$cache[$type]) && $file) {
104:                 self::$cache['*'][$file] = filemtime($file);
105:                 self::parseScript($file);
106:                 $outerCache->save('list', self::$cache);
107:             }
108: 
109:             if (isset(self::$cache[$type][$member])) {
110:                 $annotations = self::$cache[$type][$member];
111:             } else {
112:                 $annotations = array();
113:             }
114:         }
115: 
116:         if ($r instanceof \ReflectionMethod && !$r->isPrivate()
117:             && (!$r->isConstructor() || !empty($annotations['inheritdoc'][0])))
118:         {
119:             try {
120:                 $inherited = self::getAll(new \ReflectionMethod(get_parent_class($type), $member));
121:             } catch (\ReflectionException $e) {
122:                 try {
123:                     $inherited = self::getAll($r->getPrototype());
124:                 } catch (\ReflectionException $e) {
125:                     $inherited = array();
126:                 }
127:             }
128:             $annotations += array_intersect_key($inherited, array_flip(self::$inherited));
129:         }
130: 
131:         return self::$cache[$type][$member] = $annotations;
132:     }
133: 
134: 
135:     /**
136:      * Parses phpDoc comment.
137:      * @param  string
138:      * @return array
139:      */
140:     private static function parseComment($comment)
141:     {
142:         static $tokens = array('true' => TRUE, 'false' => FALSE, 'null' => NULL, '' => TRUE);
143: 
144:         $res = array();
145:         $comment = preg_replace('#^\s*\*\s?#ms', '', trim($comment, '/*'));
146:         $parts = preg_split('#^\s*(?=@'.self::RE_IDENTIFIER.')#m', $comment, 2);
147: 
148:         $description = trim($parts[0]);
149:         if ($description !== '') {
150:             $res['description'] = array($description);
151:         }
152: 
153:         $matches = Strings::matchAll(
154:             isset($parts[1]) ? $parts[1] : '',
155:             '~
156:                 (?<=\s|^)@('.self::RE_IDENTIFIER.')[ \t]*      ##  annotation
157:                 (
158:                     \((?>'.self::RE_STRING.'|[^\'")@]+)+\)|  ##  (value)
159:                     [^(@\r\n][^@\r\n]*|)                     ##  value
160:             ~xi'
161:         );
162: 
163:         foreach ($matches as $match) {
164:             list(, $name, $value) = $match;
165: 
166:             if (substr($value, 0, 1) === '(') {
167:                 $items = array();
168:                 $key = '';
169:                 $val = TRUE;
170:                 $value[0] = ',';
171:                 while ($m = Strings::match(
172:                     $value,
173:                     '#\s*,\s*(?>(' . self::RE_IDENTIFIER . ')\s*=\s*)?(' . self::RE_STRING . '|[^\'"),\s][^\'"),]*)#A')
174:                 ) {
175:                     $value = substr($value, strlen($m[0]));
176:                     list(, $key, $val) = $m;
177:                     $val = rtrim($val);
178:                     if ($val[0] === "'" || $val[0] === '"') {
179:                         $val = substr($val, 1, -1);
180: 
181:                     } elseif (is_numeric($val)) {
182:                         $val = 1 * $val;
183: 
184:                     } else {
185:                         $lval = strtolower($val);
186:                         $val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val;
187:                     }
188: 
189:                     if ($key === '') {
190:                         $items[] = $val;
191: 
192:                     } else {
193:                         $items[$key] = $val;
194:                     }
195:                 }
196: 
197:                 $value = count($items) < 2 && $key === '' ? $val : $items;
198: 
199:             } else {
200:                 $value = trim($value);
201:                 if (is_numeric($value)) {
202:                     $value = 1 * $value;
203: 
204:                 } else {
205:                     $lval = strtolower($value);
206:                     $value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value;
207:                 }
208:             }
209: 
210:             $class = $name . 'Annotation';
211:             if (class_exists($class)) {
212:                 $res[$name][] = new $class(is_array($value) ? $value : array('value' => $value));
213: 
214:             } else {
215:                 $res[$name][] = is_array($value) ? new \ArrayObject($value, \ArrayObject::ARRAY_AS_PROPS) : $value;
216:             }
217:         }
218: 
219:         return $res;
220:     }
221: 
222: 
223:     /**
224:      * Parses PHP file.
225:      * @param  string
226:      * @return void
227:      */
228:     private static function parseScript($file)
229:     {
230:         $T_NAMESPACE = PHP_VERSION_ID < 50300 ? -1 : T_NAMESPACE;
231:         $T_NS_SEPARATOR = PHP_VERSION_ID < 50300 ? -1 : T_NS_SEPARATOR;
232: 
233:         $s = file_get_contents($file);
234: 
235:         if (Strings::match($s, '#//nette'.'loader=(\S*)#')) {
236:             return;
237:         }
238: 
239:         $expected = $namespace = $class = $docComment = NULL;
240:         $level = $classLevel = 0;
241: 
242:         foreach (token_get_all($s) as $token) {
243: 
244:             if (is_array($token)) {
245:                 switch ($token[0]) {
246:                     case T_DOC_COMMENT:
247:                         $docComment = $token[1];
248:                         // break intentionally omitted
249:                     case T_WHITESPACE:
250:                     case T_COMMENT:
251:                         continue 2;
252: 
253:                     case T_STRING:
254:                     case $T_NS_SEPARATOR:
255:                     case T_VARIABLE:
256:                         if ($expected) {
257:                             $name .= $token[1];
258:                         }
259:                         continue 2;
260: 
261:                     case T_FUNCTION:
262:                     case T_VAR:
263:                     case T_PUBLIC:
264:                     case T_PROTECTED:
265:                     case $T_NAMESPACE:
266:                     case T_CLASS:
267:                     case T_INTERFACE:
268:                         $expected = $token[0];
269:                         $name = NULL;
270:                         continue 2;
271: 
272:                     case T_STATIC:
273:                     case T_ABSTRACT:
274:                     case T_FINAL:
275:                         continue 2; // ignore in expectation
276: 
277:                     case T_CURLY_OPEN:
278:                     case T_DOLLAR_OPEN_CURLY_BRACES:
279:                         $level++;
280:                 }
281:             }
282: 
283:             if ($expected) {
284:                 switch ($expected) {
285:                     case T_CLASS:
286:                     case T_INTERFACE:
287:                         $class = $namespace . $name;
288:                         $classLevel = $level;
289:                         $name = '';
290:                         // break intentionally omitted
291:                     case T_FUNCTION:
292:                         if ($token === '&') {
293:                             continue 2; // ignore
294:                         }
295:                     case T_VAR:
296:                     case T_PUBLIC:
297:                     case T_PROTECTED:
298:                         if ($class && $name !== NULL && $docComment) {
299:                             self::$cache[$class][$name] = self::parseComment($docComment);
300:                         }
301:                         break;
302: 
303:                     case $T_NAMESPACE:
304:                         $namespace = $name . '\\';
305:                 }
306: 
307:                 $expected = $docComment = NULL;
308:             }
309: 
310:             if ($token === ';') {
311:                 $docComment = NULL;
312:             } elseif ($token === '{') {
313:                 $docComment = NULL;
314:                 $level++;
315:             } elseif ($token === '}') {
316:                 $level--;
317:                 if ($level === $classLevel) {
318:                     $class = NULL;
319:                 }
320:             }
321:         }
322:     }
323: 
324: 
325:     /********************* backend ****************d*g**/
326: 
327: 
328:     /**
329:      * @return void
330:      */
331:     public static function setCacheStorage(Nette\Caching\IStorage $storage)
332:     {
333:         self::$cacheStorage = $storage;
334:     }
335: 
336: 
337:     /**
338:      * @return Nette\Caching\IStorage
339:      */
340:     public static function getCacheStorage()
341:     {
342:         return self::$cacheStorage;
343:     }
344: 
345: }
346: 
Nette 2.0 API documentation generated by ApiGen 2.8.0