Completed
Push — master ( c5cc72...7c6f56 )
by Jean-Christophe
02:02
created

Addendum::resolveClassName()   C

Complexity

Conditions 8
Paths 13

Size

Total Lines 22
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 6.6037
c 0
b 0
f 0
cc 8
eloc 17
nc 13
nop 1
1
<?php
2
	/**
3
	 * Addendum PHP Reflection Annotations
4
	 * http://code.google.com/p/addendum/
5
	 *
6
	 * Copyright (C) 2006-2009 Jan "johno Suchal <[email protected]>
7
8
	 * This library is free software; you can redistribute it and/or
9
	 * modify it under the terms of the GNU Lesser General Public
10
	 * License as published by the Free Software Foundation; either
11
	 * version 2.1 of the License, or (at your option) any later version.
12
13
	 * This library is distributed in the hope that it will be useful,
14
	 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
	 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
	 * Lesser General Public License for more details.
17
18
	 * You should have received a copy of the GNU Lesser General Public
19
	 * License along with this library; if not, write to the Free Software
20
	 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21
	 **/
22
23
	require_once(dirname(__FILE__).'/annotations/annotation_parser.php');
24
25
	class Annotation {
26
		public $value;
27
		private static $creationStack = array();
28
29
		public final function __construct($data = array(), $target = false) {
30
			$reflection = new ReflectionClass($this);
31
			$class = $reflection->getName();
32
			if(isset(self::$creationStack[$class])) {
33
				trigger_error("Circular annotation reference on '$class'", E_USER_ERROR);
34
				return;
35
			}
36
			self::$creationStack[$class] = true;
37
			foreach($data as $key => $value) {
38
				if($reflection->hasProperty($key)) {
39
					$this->$key = $value;
40
				} else {
41
					trigger_error("Property '$key' not defined for annotation '$class'");
42
				}
43
			}
44
			$this->checkTargetConstraints($target);
45
			$this->checkConstraints($target);
46
			unset(self::$creationStack[$class]);
47
		}
48
49
		private function checkTargetConstraints($target) {
50
			$reflection = new ReflectionAnnotatedClass($this);
51
			if($reflection->hasAnnotation('Target')) {
52
				$value = $reflection->getAnnotation('Target')->value;
53
				$values = is_array($value) ? $value : array($value);
54
				foreach($values as $value) {
55
					if($value == 'class' && $target instanceof ReflectionClass) return;
56
					if($value == 'method' && $target instanceof ReflectionMethod) return;
57
					if($value == 'property' && $target instanceof ReflectionProperty) return;
58
					if($value == 'nested' && $target === false) return;
59
				}
60
				if($target === false) {
61
					trigger_error("Annotation '".get_class($this)."' nesting not allowed", E_USER_ERROR);
62
				} else {
63
					trigger_error("Annotation '".get_class($this)."' not allowed on ".$this->createName($target), E_USER_ERROR);
64
				}
65
			}
66
		}
67
68
		private function createName($target) {
69
			if($target instanceof ReflectionMethod) {
70
				return $target->getDeclaringClass()->getName().'::'.$target->getName();
71
			} elseif($target instanceof ReflectionProperty) {
72
				return $target->getDeclaringClass()->getName().'::$'.$target->getName();
73
			} else {
74
				return $target->getName();
75
			}
76
		}
77
78
		protected function checkConstraints($target) {}
79
	}
80
81
	class AnnotationsCollection {
82
		private $annotations;
83
84
		public function __construct($annotations) {
85
			$this->annotations = $annotations;
86
		}
87
88
		public function hasAnnotation($class) {
89
			$class = Addendum::resolveClassName($class);
90
			return isset($this->annotations[$class]);
91
		}
92
93
		public function getAnnotation($class) {
94
			$class = Addendum::resolveClassName($class);
95
			return isset($this->annotations[$class]) ? end($this->annotations[$class]) : false;
96
		}
97
98
		public function getAnnotations() {
99
			$result = array();
100
			foreach($this->annotations as $instances) {
101
				$result[] = end($instances);
102
			}
103
			return $result;
104
		}
105
106
		public function getAllAnnotations($restriction = false) {
107
			$restriction = Addendum::resolveClassName($restriction);
108
			$result = array();
109
			foreach($this->annotations as $class => $instances) {
110
				if(!$restriction || $restriction == $class) {
111
					$result = array_merge($result, $instances);
112
				}
113
			}
114
			return $result;
115
		}
116
	}
117
118
	class Annotation_Target extends Annotation {}
119
120
	class AnnotationsBuilder {
121
		private static $cache = array();
122
123
		public function build($targetReflection) {
124
			$data = $this->parse($targetReflection);
125
			$annotations = array();
126
			foreach($data as $class => $parameters) {
127
				foreach($parameters as $params) {
128
					$annotation = $this->instantiateAnnotation($class, $params, $targetReflection);
129
					if($annotation !== false) {
130
						$annotations[get_class($annotation)][] = $annotation;
131
					}
132
				}
133
			}
134
			return new AnnotationsCollection($annotations);
135
		}
136
137
		public function instantiateAnnotation($class, $parameters, $targetReflection = false) {
138
			$class = Addendum::resolveClassName($class);
139
			if(is_subclass_of($class, 'Annotation') && !Addendum::ignores($class) || $class == 'Annotation') {
140
				$annotationReflection = new ReflectionClass($class);
141
				return $annotationReflection->newInstance($parameters, $targetReflection);
142
			}
143
			return false;
144
		}
145
146
		private function parse($reflection) {
147
			$key = $this->createName($reflection);
148
			if(!isset(self::$cache[$key])) {
149
				$parser = new AnnotationsMatcher;
150
				$parser->matches($this->getDocComment($reflection), $data);
151
				self::$cache[$key] = $data;
152
			}
153
			return self::$cache[$key];
154
		}
155
156
		private function createName($target) {
157
			if($target instanceof ReflectionMethod) {
158
				return $target->getDeclaringClass()->getName().'::'.$target->getName();
159
			} elseif($target instanceof ReflectionProperty) {
160
				return $target->getDeclaringClass()->getName().'::$'.$target->getName();
161
			} else {
162
				return $target->getName();
163
			}
164
		}
165
166
		protected function getDocComment($reflection) {
167
			return Addendum::getDocComment($reflection);
168
		}
169
170
		public static function clearCache() {
171
			self::$cache = array();
172
		}
173
	}
174
175
	class ReflectionAnnotatedClass extends ReflectionClass {
176
		private $annotations;
177
178
		public function __construct($class) {
179
			parent::__construct($class);
180
			$this->annotations = $this->createAnnotationBuilder()->build($this);
181
		}
182
183
		public function hasAnnotation($class) {
184
			return $this->annotations->hasAnnotation($class);
185
		}
186
187
		public function getAnnotation($annotation) {
188
			return $this->annotations->getAnnotation($annotation);
189
		}
190
191
		public function getAnnotations() {
192
			return $this->annotations->getAnnotations();
193
		}
194
195
		public function getAllAnnotations($restriction = false) {
196
			return $this->annotations->getAllAnnotations($restriction);
197
		}
198
199
		public function getConstructor() {
200
			return $this->createReflectionAnnotatedMethod(parent::getConstructor());
201
		}
202
203
		public function getMethod($name) {
204
			return $this->createReflectionAnnotatedMethod(parent::getMethod($name));
205
		}
206
207
		public function getMethods($filter = -1) {
208
			$result = array();
209
			foreach(parent::getMethods($filter) as $method) {
210
				$result[] = $this->createReflectionAnnotatedMethod($method);
211
			}
212
			return $result;
213
		}
214
215
		public function getProperty($name) {
216
			return $this->createReflectionAnnotatedProperty(parent::getProperty($name));
217
		}
218
219
		public function getProperties($filter = -1) {
220
			$result = array();
221
			foreach(parent::getProperties($filter) as $property) {
222
				$result[] = $this->createReflectionAnnotatedProperty($property);
223
			}
224
			return $result;
225
		}
226
227
		public function getInterfaces() {
228
			$result = array();
229
			foreach(parent::getInterfaces() as $interface) {
230
				$result[] = $this->createReflectionAnnotatedClass($interface);
231
			}
232
			return $result;
233
		}
234
235
		public function getParentClass() {
236
			$class = parent::getParentClass();
237
			return $this->createReflectionAnnotatedClass($class);
238
		}
239
240
		protected function createAnnotationBuilder() {
241
			return new AnnotationsBuilder();
242
		}
243
244
		private function createReflectionAnnotatedClass($class) {
245
			return ($class !== false) ? new ReflectionAnnotatedClass($class->getName()) : false;
246
		}
247
248
		private function createReflectionAnnotatedMethod($method) {
249
			return ($method !== null) ? new ReflectionAnnotatedMethod($this->getName(), $method->getName()) : null;
250
		}
251
252
		private function createReflectionAnnotatedProperty($property) {
253
			return ($property !== null) ? new ReflectionAnnotatedProperty($this->getName(), $property->getName()) : null;
254
		}
255
	}
256
257
	class ReflectionAnnotatedMethod extends ReflectionMethod {
258
		private $annotations;
259
260
		public function __construct($class, $name) {
261
			parent::__construct($class, $name);
262
			$this->annotations = $this->createAnnotationBuilder()->build($this);
263
		}
264
265
		public function hasAnnotation($class) {
266
			return $this->annotations->hasAnnotation($class);
267
		}
268
269
		public function getAnnotation($annotation) {
270
			return $this->annotations->getAnnotation($annotation);
271
		}
272
273
		public function getAnnotations() {
274
			return $this->annotations->getAnnotations();
275
		}
276
277
		public function getAllAnnotations($restriction = false) {
278
			return $this->annotations->getAllAnnotations($restriction);
279
		}
280
281
		public function getDeclaringClass() {
282
			$class = parent::getDeclaringClass();
283
			return new ReflectionAnnotatedClass($class->getName());
284
		}
285
286
		protected function createAnnotationBuilder() {
287
			return new AnnotationsBuilder();
288
		}
289
	}
290
291
	class ReflectionAnnotatedProperty extends ReflectionProperty {
292
		private $annotations;
293
294
		public function __construct($class, $name) {
295
			parent::__construct($class, $name);
296
			$this->annotations = $this->createAnnotationBuilder()->build($this);
297
		}
298
299
		public function hasAnnotation($class) {
300
			return $this->annotations->hasAnnotation($class);
301
		}
302
303
		public function getAnnotation($annotation) {
304
			return $this->annotations->getAnnotation($annotation);
305
		}
306
307
		public function getAnnotations() {
308
			return $this->annotations->getAnnotations();
309
		}
310
311
		public function getAllAnnotations($restriction = false) {
312
			return $this->annotations->getAllAnnotations($restriction);
313
		}
314
315
		public function getDeclaringClass() {
316
			$class = parent::getDeclaringClass();
317
			return new ReflectionAnnotatedClass($class->getName());
318
		}
319
320
		protected function createAnnotationBuilder() {
321
			return new AnnotationsBuilder();
322
		}
323
	}
324
325
	class Addendum {
326
		private static $rawMode;
327
		private static $ignore;
328
		private static $classnames = array();
329
		private static $annotations = false;
330
331
		public static function getDocComment($reflection) {
332
			if(self::checkRawDocCommentParsingNeeded()) {
333
				$docComment = new DocComment();
334
				return $docComment->get($reflection);
335
			} else {
336
				return $reflection->getDocComment();
337
			}
338
		}
339
340
		/** Raw mode test */
341
		private static function checkRawDocCommentParsingNeeded() {
342
			if(self::$rawMode === null) {
343
				$reflection = new ReflectionClass('Addendum');
344
				$method = $reflection->getMethod('checkRawDocCommentParsingNeeded');
345
				self::setRawMode($method->getDocComment() === false);
346
			}
347
			return self::$rawMode;
348
		}
349
350
		public static function setRawMode($enabled = true) {
351
			if($enabled) {
352
				require_once(dirname(__FILE__).'/annotations/doc_comment.php');
353
			}
354
			self::$rawMode = $enabled;
355
		}
356
357
		public static function resetIgnoredAnnotations() {
358
			self::$ignore = array();
359
		}
360
361
		public static function ignores($class) {
362
			return isset(self::$ignore[$class]);
363
		}
364
365
		public static function ignore() {
366
			foreach(func_get_args() as $class) {
367
				self::$ignore[$class] = true;
368
			}
369
		}
370
371
		public static function resolveClassName($class) {
372
			if(isset(self::$classnames[$class])) return self::$classnames[$class];
373
			$matching = array();
374
			foreach(self::getDeclaredAnnotations() as $declared) {
375
				if($declared == $class) {
376
					$matching[] = $declared;
377
				} else {
378
					$pos = strrpos($declared, "_$class");
379
					if($pos !== false && ($pos + strlen($class) == strlen($declared) - 1)) {
380
						$matching[] = $declared;
381
					}
382
				}
383
			}
384
			$result = null;
385
			switch(count($matching)) {
386
				case 0: $result = $class; break;
387
				case 1: $result = $matching[0]; break;
388
				default: trigger_error("Cannot resolve class name for '$class'. Possible matches: " . join(', ', $matching), E_USER_ERROR);
389
			}
390
			self::$classnames[$class] = $result;
391
			return $result;
392
		}
393
394
		private static function getDeclaredAnnotations() {
395
			if(!self::$annotations) {
396
				self::$annotations = array();
397
				foreach(get_declared_classes() as $class) {
398
					if(is_subclass_of($class, 'Annotation') || $class == 'Annotation') self::$annotations[] = $class;
399
				}
400
			}
401
			return self::$annotations;
402
		}
403
404
405
	}
406