1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15:
16: namespace TokenReflection;
17:
18: use TokenReflection\Exception, TokenReflection\Stream\StreamBase as Stream;
19: use ReflectionParameter as InternalReflectionParameter;
20:
21: 22: 23:
24: class ReflectionParameter extends ReflectionElement implements IReflectionParameter
25: {
26: 27: 28: 29: 30:
31: const ARRAY_TYPE_HINT = 'array';
32:
33: 34: 35: 36: 37:
38: const CALLABLE_TYPE_HINT = 'callable';
39:
40: 41: 42: 43: 44:
45: private $declaringClassName;
46:
47: 48: 49: 50: 51:
52: private $declaringFunctionName;
53:
54: 55: 56: 57: 58:
59: private $defaultValue;
60:
61: 62: 63: 64: 65:
66: private $defaultValueDefinition = array();
67:
68: 69: 70: 71: 72:
73: private $typeHint;
74:
75: 76: 77: 78: 79:
80: private $originalTypeHint;
81:
82: 83: 84: 85: 86:
87: private $position;
88:
89: 90: 91: 92: 93:
94: private $isOptional;
95:
96: 97: 98: 99: 100:
101: private $passedByReference = false;
102:
103: 104: 105: 106: 107:
108: public function getDeclaringClass()
109: {
110: return null === $this->declaringClassName ? null : $this->getBroker()->getClass($this->declaringClassName);
111: }
112:
113: 114: 115: 116: 117:
118: public function getDeclaringClassName()
119: {
120: return $this->declaringClassName;
121: }
122:
123: 124: 125: 126: 127:
128: public function getDeclaringFunction()
129: {
130: if (null !== $this->declaringClassName) {
131:
132: $class = $this->getBroker()->getClass($this->declaringClassName);
133: if (null !== $class) {
134: return $class->getMethod($this->declaringFunctionName);
135: }
136: } else {
137:
138: return $this->getBroker()->getFunction($this->declaringFunctionName);
139: }
140: }
141:
142: 143: 144: 145: 146:
147: public function getDeclaringFunctionName()
148: {
149: return $this->declaringFunctionName;
150: }
151:
152: 153: 154: 155: 156: 157: 158:
159: public function getDefaultValue()
160: {
161: if (!$this->isOptional()) {
162: throw new Exception\RuntimeException('Property is not optional.', Exception\RuntimeException::UNSUPPORTED, $this);
163: }
164:
165: if (is_array($this->defaultValueDefinition)) {
166: if (0 === count($this->defaultValueDefinition)) {
167: throw new Exception\RuntimeException('Property has no default value.', Exception\RuntimeException::DOES_NOT_EXIST, $this);
168: }
169:
170: $this->defaultValue = Resolver::getValueDefinition($this->defaultValueDefinition, $this);
171: $this->defaultValueDefinition = Resolver::getSourceCode($this->defaultValueDefinition);
172: }
173:
174: return $this->defaultValue;
175: }
176:
177: 178: 179: 180: 181:
182: public function getDefaultValueDefinition()
183: {
184: return is_array($this->defaultValueDefinition) ? Resolver::getSourceCode($this->defaultValueDefinition) : $this->defaultValueDefinition;
185: }
186:
187: 188: 189: 190: 191:
192: public function isDefaultValueAvailable()
193: {
194: return null !== $this->getDefaultValueDefinition();
195: }
196:
197: 198: 199: 200: 201:
202: public function getPosition()
203: {
204: return $this->position;
205: }
206:
207: 208: 209: 210: 211:
212: public function isArray()
213: {
214: return $this->typeHint === self::ARRAY_TYPE_HINT;
215: }
216:
217: 218: 219: 220: 221:
222: public function isCallable()
223: {
224: return $this->typeHint === self::CALLABLE_TYPE_HINT;
225: }
226:
227: 228: 229: 230: 231:
232: public function getOriginalTypeHint()
233: {
234: return !$this->isArray() && !$this->isCallable() ? ltrim($this->originalTypeHint, '\\') : null;
235: }
236:
237: 238: 239: 240: 241:
242: public function getClass()
243: {
244: $name = $this->getClassName();
245: if (null === $name) {
246: return null;
247: }
248:
249: return $this->getBroker()->getClass($name);
250: }
251:
252: 253: 254: 255: 256: 257:
258: public function getClassName()
259: {
260: if ($this->isArray() || $this->isCallable()) {
261: return null;
262: }
263:
264: if (null === $this->typeHint && null !== $this->originalTypeHint) {
265: if (null !== $this->declaringClassName) {
266: $parent = $this->getDeclaringClass();
267: if (null === $parent) {
268: throw new Exception\RuntimeException('Could not load class reflection.', Exception\RuntimeException::DOES_NOT_EXIST, $this);
269: }
270: } else {
271: $parent = $this->getDeclaringFunction();
272: if (null === $parent || !$parent->isTokenized()) {
273: throw new Exception\RuntimeException('Could not load function reflection.', Exception\RuntimeException::DOES_NOT_EXIST, $this);
274: }
275: }
276:
277: $lTypeHint = strtolower($this->originalTypeHint);
278: if ('parent' === $lTypeHint || 'self' === $lTypeHint) {
279: if (null === $this->declaringClassName) {
280: throw new Exception\RuntimeException('Parameter type hint cannot be "self" nor "parent" when not a method.', Exception\RuntimeException::UNSUPPORTED, $this);
281: }
282:
283: if ('parent' === $lTypeHint) {
284: if ($parent->isInterface() || null === $parent->getParentClassName()) {
285: throw new Exception\RuntimeException('Class has no parent.', Exception\RuntimeException::DOES_NOT_EXIST, $this);
286: }
287:
288: $this->typeHint = $parent->getParentClassName();
289: } else {
290: $this->typeHint = $this->declaringClassName;
291: }
292: } else {
293: $this->typeHint = ltrim(Resolver::resolveClassFQN($this->originalTypeHint, $parent->getNamespaceAliases(), $parent->getNamespaceName()), '\\');
294: }
295: }
296:
297: return $this->typeHint;
298: }
299:
300: 301: 302: 303: 304:
305: public function allowsNull()
306: {
307: if ($this->isArray() || $this->isCallable()) {
308: return 'null' === strtolower($this->getDefaultValueDefinition());
309: }
310:
311: return null === $this->originalTypeHint || !empty($this->defaultValueDefinition);
312: }
313:
314: 315: 316: 317: 318: 319:
320: public function isOptional()
321: {
322: if (null === $this->isOptional) {
323: $function = $this->getDeclaringFunction();
324: if (null === $function) {
325: throw new Exception\RuntimeException('Could not get the declaring function reflection.', Exception\RuntimeException::DOES_NOT_EXIST, $this);
326: }
327:
328: $this->isOptional = true;
329: foreach (array_slice($function->getParameters(), $this->position) as $reflectionParameter) {
330: if (!$reflectionParameter->isDefaultValueAvailable()) {
331: $this->isOptional = false;
332: break;
333: }
334: }
335: }
336:
337: return $this->isOptional;
338: }
339:
340: 341: 342: 343: 344:
345: public function isPassedByReference()
346: {
347: return $this->passedByReference;
348: }
349:
350: 351: 352: 353: 354:
355: public function canBePassedByValue()
356: {
357: return !$this->isPassedByReference();
358: }
359:
360: 361: 362: 363: 364:
365: public function getPrettyName()
366: {
367: return str_replace('()', '($' . $this->name . ')', $this->getDeclaringFunction()->getPrettyName());
368: }
369:
370: 371: 372: 373: 374:
375: public function __toString()
376: {
377: if ($this->getClass()) {
378: $hint = $this->getClassName();
379: } elseif ($this->isArray()) {
380: $hint = self::ARRAY_TYPE_HINT;
381: } elseif ($this->isCallable()) {
382: $hint = self::CALLABLE_TYPE_HINT;
383: } else {
384: $hint = '';
385: }
386:
387: if (!empty($hint) && $this->allowsNull()) {
388: $hint .= ' or NULL';
389: }
390:
391: if ($this->isDefaultValueAvailable()) {
392: $default = ' = ';
393: if (null === $this->getDefaultValue()) {
394: $default .= 'NULL';
395: } elseif (is_array($this->getDefaultValue())) {
396: $default .= 'Array';
397: } elseif (is_bool($this->getDefaultValue())) {
398: $default .= $this->getDefaultValue() ? 'true' : 'false';
399: } elseif (is_string($this->getDefaultValue())) {
400: $default .= sprintf("'%s'", str_replace("'", "\\'", $this->getDefaultValue()));
401: } else {
402: $default .= $this->getDefaultValue();
403: }
404: } else {
405: $default = '';
406: }
407:
408: return sprintf(
409: 'Parameter #%d [ <%s> %s%s$%s%s ]',
410: $this->getPosition(),
411: $this->isOptional() ? 'optional' : 'required',
412: $hint ? $hint . ' ' : '',
413: $this->isPassedByReference() ? '&' : '',
414: $this->getName(),
415: $default
416: );
417: }
418:
419: 420: 421: 422: 423: 424: 425: 426: 427: 428:
429: public static function export(Broker $broker, $function, $parameter, $return = false)
430: {
431: $functionName = $function;
432: $parameterName = $parameter;
433:
434: $function = $broker->getFunction($functionName);
435: if (null === $function) {
436: throw new Exception\RuntimeException(sprintf('Function %s() does not exist.', $functionName), Exception\RuntimeException::DOES_NOT_EXIST);
437: }
438: $parameter = $function->getParameter($parameterName);
439:
440: if ($return) {
441: return $parameter->__toString();
442: }
443:
444: echo $parameter->__toString();
445: }
446:
447: 448: 449: 450: 451:
452: public function getNamespaceAliases()
453: {
454: return $this->getDeclaringFunction()->getNamespaceAliases();
455: }
456:
457: 458: 459: 460: 461: 462:
463: public function alias(ReflectionMethod $parent)
464: {
465: $parameter = clone $this;
466:
467: $parameter->declaringClassName = $parent->getDeclaringClassName();
468: $parameter->declaringFunctionName = $parent->getName();
469:
470: return $parameter;
471: }
472:
473: 474: 475: 476: 477: 478: 479: 480:
481: protected function processParent(IReflection $parent, Stream $tokenStream)
482: {
483: if (!$parent instanceof ReflectionFunctionBase) {
484: throw new Exception\ParseException($this, $tokenStream, 'The parent object has to be an instance of TokenReflection\ReflectionFunctionBase.', Exception\ParseException::INVALID_PARENT);
485: }
486:
487:
488: $this->declaringFunctionName = $parent->getName();
489:
490:
491: $this->position = count($parent->getParameters());
492:
493:
494: if ($parent instanceof ReflectionMethod) {
495: $this->declaringClassName = $parent->getDeclaringClassName();
496: }
497:
498: return parent::processParent($parent, $tokenStream);
499: }
500:
501: 502: 503: 504: 505: 506: 507:
508: protected function parse(Stream $tokenStream, IReflection $parent)
509: {
510: return $this
511: ->parseTypeHint($tokenStream)
512: ->parsePassedByReference($tokenStream)
513: ->parseName($tokenStream)
514: ->parseDefaultValue($tokenStream);
515: }
516:
517: 518: 519: 520: 521: 522: 523:
524: private function parseTypeHint(Stream $tokenStream)
525: {
526: $type = $tokenStream->getType();
527:
528: if (T_ARRAY === $type) {
529: $this->typeHint = self::ARRAY_TYPE_HINT;
530: $this->originalTypeHint = self::ARRAY_TYPE_HINT;
531: $tokenStream->skipWhitespaces(true);
532: } elseif (T_CALLABLE === $type) {
533: $this->typeHint = self::CALLABLE_TYPE_HINT;
534: $this->originalTypeHint = self::CALLABLE_TYPE_HINT;
535: $tokenStream->skipWhitespaces(true);
536: } elseif (T_STRING === $type || T_NS_SEPARATOR === $type) {
537: $className = '';
538: do {
539: $className .= $tokenStream->getTokenValue();
540:
541: $tokenStream->skipWhitespaces(true);
542: $type = $tokenStream->getType();
543: } while (T_STRING === $type || T_NS_SEPARATOR === $type);
544:
545: if ('' === ltrim($className, '\\')) {
546: throw new Exception\ParseException($this, $tokenStream, sprintf('Invalid class name definition: "%s".', $className), Exception\ParseException::LOGICAL_ERROR);
547: }
548:
549: $this->originalTypeHint = $className;
550: }
551:
552: return $this;
553: }
554:
555: 556: 557: 558: 559: 560:
561: private function parsePassedByReference(Stream $tokenStream)
562: {
563: if ($tokenStream->is('&')) {
564: $this->passedByReference = true;
565: $tokenStream->skipWhitespaces(true);
566: }
567:
568: return $this;
569: }
570:
571: 572: 573: 574: 575: 576: 577:
578: protected function parseName(Stream $tokenStream)
579: {
580: if (!$tokenStream->is(T_VARIABLE)) {
581: throw new Exception\ParseException($this, $tokenStream, 'The parameter name could not be determined.', Exception\ParseException::UNEXPECTED_TOKEN);
582: }
583:
584: $this->name = substr($tokenStream->getTokenValue(), 1);
585:
586: $tokenStream->skipWhitespaces(true);
587:
588: return $this;
589: }
590:
591: 592: 593: 594: 595: 596: 597:
598: private function parseDefaultValue(Stream $tokenStream)
599: {
600: if ($tokenStream->is('=')) {
601: $tokenStream->skipWhitespaces(true);
602:
603: $level = 0;
604: while (null !== ($type = $tokenStream->getType())) {
605: switch ($type) {
606: case ')':
607: if (0 === $level) {
608: break 2;
609: }
610: case '}':
611: case ']':
612: $level--;
613: break;
614: case '(':
615: case '{':
616: case '[':
617: $level++;
618: break;
619: case ',':
620: if (0 === $level) {
621: break 2;
622: }
623: break;
624: default:
625: break;
626: }
627:
628: $this->defaultValueDefinition[] = $tokenStream->current();
629: $tokenStream->next();
630: }
631:
632: if (')' !== $type && ',' !== $type) {
633: throw new Exception\ParseException($this, $tokenStream, 'The property default value is not terminated properly. Expected "," or ")".', Exception\ParseException::UNEXPECTED_TOKEN);
634: }
635: }
636:
637: return $this;
638: }
639: }
640: