Overview

Namespaces

  • TokenReflection
    • Broker
      • Backend
    • Dummy
    • Exception
    • Invalid
    • Php
    • Stream

Classes

  • Broker
  • ReflectionAnnotation
  • ReflectionBase
  • ReflectionClass
  • ReflectionConstant
  • ReflectionElement
  • ReflectionFile
  • ReflectionFileNamespace
  • ReflectionFunction
  • ReflectionFunctionBase
  • ReflectionMethod
  • ReflectionNamespace
  • ReflectionParameter
  • ReflectionProperty
  • Resolver

Interfaces

  • IReflection
  • IReflectionClass
  • IReflectionConstant
  • IReflectionExtension
  • IReflectionFunction
  • IReflectionFunctionBase
  • IReflectionMethod
  • IReflectionNamespace
  • IReflectionParameter
  • IReflectionProperty
  • Overview
  • Namespace
  • Class
  • Tree
  • Download
  1: <?php
  2: /**
  3:  * PHP Token Reflection
  4:  *
  5:  * Version 1.3.1
  6:  *
  7:  * LICENSE
  8:  *
  9:  * This source file is subject to the new BSD license that is bundled
 10:  * with this library in the file LICENSE.
 11:  *
 12:  * @author Ondřej Nešpor
 13:  * @author Jaroslav Hanslík
 14:  */
 15: 
 16: namespace TokenReflection;
 17: 
 18: use TokenReflection\Exception, TokenReflection\Stream\StreamBase as Stream;
 19: use ReflectionParameter as InternalReflectionParameter;
 20: 
 21: /**
 22:  * Tokenized function/method parameter reflection.
 23:  */
 24: class ReflectionParameter extends ReflectionElement implements IReflectionParameter
 25: {
 26:     /**
 27:      * The parameter requires an array as its value.
 28:      *
 29:      * @var string
 30:      */
 31:     const ARRAY_TYPE_HINT = 'array';
 32: 
 33:     /**
 34:      * The parameter requires a callback definition as its value.
 35:      *
 36:      * @var string
 37:      */
 38:     const CALLABLE_TYPE_HINT = 'callable';
 39: 
 40:     /**
 41:      * Declaring class name.
 42:      *
 43:      * @var string
 44:      */
 45:     private $declaringClassName;
 46: 
 47:     /**
 48:      * Declaring function name.
 49:      *
 50:      * @var string
 51:      */
 52:     private $declaringFunctionName;
 53: 
 54:     /**
 55:      * Parameter default value.
 56:      *
 57:      * @var mixed
 58:      */
 59:     private $defaultValue;
 60: 
 61:     /**
 62:      * Parameter default value definition (part of the source code).
 63:      *
 64:      * @var array|string
 65:      */
 66:     private $defaultValueDefinition = array();
 67: 
 68:     /**
 69:      * Defines a type hint (class name or array) of parameter values.
 70:      *
 71:      * @var string
 72:      */
 73:     private $typeHint;
 74: 
 75:     /**
 76:      * Defines a type hint (class name, array or callable) of parameter values as it was defined.
 77:      *
 78:      * @var string
 79:      */
 80:     private $originalTypeHint;
 81: 
 82:     /**
 83:      * Position of the parameter in the function/method.
 84:      *
 85:      * @var integer
 86:      */
 87:     private $position;
 88: 
 89:     /**
 90:      * Determines if the parameter is optional.
 91:      *
 92:      * @var boolean
 93:      */
 94:     private $isOptional;
 95: 
 96:     /**
 97:      * Determines if the value is passed by reference.
 98:      *
 99:      * @var boolean
100:      */
101:     private $passedByReference = false;
102: 
103:     /**
104:      * Returns the declaring class.
105:      *
106:      * @return \TokenReflection\ReflectionClass|null
107:      */
108:     public function getDeclaringClass()
109:     {
110:         return null === $this->declaringClassName ? null : $this->getBroker()->getClass($this->declaringClassName);
111:     }
112: 
113:     /**
114:      * Returns the declaring class name.
115:      *
116:      * @return string|null
117:      */
118:     public function getDeclaringClassName()
119:     {
120:         return $this->declaringClassName;
121:     }
122: 
123:     /**
124:      * Returns the declaring function.
125:      *
126:      * @return \TokenReflection\ReflectionFunctionBase
127:      */
128:     public function getDeclaringFunction()
129:     {
130:         if (null !== $this->declaringClassName) {
131:             // Method parameter
132:             $class = $this->getBroker()->getClass($this->declaringClassName);
133:             if (null !== $class) {
134:                 return $class->getMethod($this->declaringFunctionName);
135:             }
136:         } else {
137:             // Function parameter
138:             return $this->getBroker()->getFunction($this->declaringFunctionName);
139:         }
140:     }
141: 
142:     /**
143:      * Returns the declaring function name.
144:      *
145:      * @return string
146:      */
147:     public function getDeclaringFunctionName()
148:     {
149:         return $this->declaringFunctionName;
150:     }
151: 
152:     /**
153:      * Returns the default value.
154:      *
155:      * @return mixed
156:      * @throws \TokenReflection\Exception\RuntimeException If the property is not optional.
157:      * @throws \TokenReflection\Exception\RuntimeException If the property has no default value.
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:      * Returns the part of the source code defining the parameter default value.
179:      *
180:      * @return string
181:      */
182:     public function getDefaultValueDefinition()
183:     {
184:         return is_array($this->defaultValueDefinition) ? Resolver::getSourceCode($this->defaultValueDefinition) : $this->defaultValueDefinition;
185:     }
186: 
187:     /**
188:      * Retutns if a default value for the parameter is available.
189:      *
190:      * @return boolean
191:      */
192:     public function isDefaultValueAvailable()
193:     {
194:         return null !== $this->getDefaultValueDefinition();
195:     }
196: 
197:     /**
198:      * Returns the position within all parameters.
199:      *
200:      * @return integer
201:      */
202:     public function getPosition()
203:     {
204:         return $this->position;
205:     }
206: 
207:     /**
208:      * Returns if the parameter expects an array.
209:      *
210:      * @return boolean
211:      */
212:     public function isArray()
213:     {
214:         return $this->typeHint === self::ARRAY_TYPE_HINT;
215:     }
216: 
217:     /**
218:      * Returns if the parameter expects a callback.
219:      *
220:      * @return boolean
221:      */
222:     public function isCallable()
223:     {
224:         return $this->typeHint === self::CALLABLE_TYPE_HINT;
225:     }
226: 
227:     /**
228:      * Returns the original type hint as defined in the source code.
229:      *
230:      * @return string|null
231:      */
232:     public function getOriginalTypeHint()
233:     {
234:         return !$this->isArray() && !$this->isCallable() ? ltrim($this->originalTypeHint, '\\') : null;
235:     }
236: 
237:     /**
238:      * Returns reflection of the required class of the value.
239:      *
240:      * @return \TokenReflection\IReflectionClass|null
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:      * Returns the required class name of the value.
254:      *
255:      * @return string|null
256:      * @throws \TokenReflection\Exception\RuntimeException If the type hint class FQN could not be determined.
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:      * Returns if the the parameter allows NULL.
302:      *
303:      * @return boolean
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:      * Returns if the parameter is optional.
316:      *
317:      * @return boolean
318:      * @throws \TokenReflection\Exception\RuntimeException If it is not possible to determine if the parameter is optional.
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:      * Returns if the parameter value is passed by reference.
342:      *
343:      * @return boolean
344:      */
345:     public function isPassedByReference()
346:     {
347:         return $this->passedByReference;
348:     }
349: 
350:     /**
351:      * Returns if the paramter value can be passed by value.
352:      *
353:      * @return boolean
354:      */
355:     public function canBePassedByValue()
356:     {
357:         return !$this->isPassedByReference();
358:     }
359: 
360:     /**
361:      * Returns an element pretty (docblock compatible) name.
362:      *
363:      * @return string
364:      */
365:     public function getPrettyName()
366:     {
367:         return str_replace('()', '($' . $this->name . ')', $this->getDeclaringFunction()->getPrettyName());
368:     }
369: 
370:     /**
371:      * Returns the string representation of the reflection object.
372:      *
373:      * @return string
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:      * Exports a reflected object.
421:      *
422:      * @param \TokenReflection\Broker $broker Broker instance
423:      * @param string $function Function name
424:      * @param string $parameter Parameter name
425:      * @param boolean $return Return the export instead of outputting it
426:      * @return string|null
427:      * @throws \TokenReflection\Exception\RuntimeException If requested parameter doesn't exist.
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:      * Returns imported namespaces and aliases from the declaring namespace.
449:      *
450:      * @return array
451:      */
452:     public function getNamespaceAliases()
453:     {
454:         return $this->getDeclaringFunction()->getNamespaceAliases();
455:     }
456: 
457:     /**
458:      * Creates a parameter alias for the given method.
459:      *
460:      * @param \TokenReflection\ReflectionMethod $parent New parent method
461:      * @return \TokenReflection\ReflectionParameter
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:      * Processes the parent reflection object.
475:      *
476:      * @param \TokenReflection\IReflection $parent Parent reflection object
477:      * @param \TokenReflection\Stream\StreamBase $tokenStream Token substream
478:      * @return \TokenReflection\ReflectionElement
479:      * @throws \TokenReflection\Exception\ParseException If an invalid parent reflection object was provided.
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:         // Declaring function name
488:         $this->declaringFunctionName = $parent->getName();
489: 
490:         // Position
491:         $this->position = count($parent->getParameters());
492: 
493:         // Declaring class name
494:         if ($parent instanceof ReflectionMethod) {
495:             $this->declaringClassName = $parent->getDeclaringClassName();
496:         }
497: 
498:         return parent::processParent($parent, $tokenStream);
499:     }
500: 
501:     /**
502:      * Parses reflected element metadata from the token stream.
503:      *
504:      * @param \TokenReflection\Stream\StreamBase $tokenStream Token substream
505:      * @param \TokenReflection\IReflection $parent Parent reflection object
506:      * @return \TokenReflection\ReflectionParameter
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:      * Parses the type hint.
519:      *
520:      * @param \TokenReflection\Stream\StreamBase $tokenStream Token substream
521:      * @return \TokenReflection\ReflectionParameter
522:      * @throws \TokenReflection\Exception\ParseException If the type hint class name could not be determined.
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:      * Parses if parameter value is passed by reference.
557:      *
558:      * @param \TokenReflection\Stream\StreamBase $tokenStream Token substream
559:      * @return \TokenReflection\ReflectionParameter
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:      * Parses the constant name.
573:      *
574:      * @param \TokenReflection\Stream\StreamBase $tokenStream Token substream
575:      * @return \TokenReflection\ReflectionParameter
576:      * @throws \TokenReflection\Exception\ParseException If the parameter name could not be determined.
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:      * Parses the parameter default value.
593:      *
594:      * @param \TokenReflection\Stream\StreamBase $tokenStream Token substream
595:      * @return \TokenReflection\ReflectionParameter
596:      * @throws \TokenReflection\Exception\ParseException If the default value could not be determined.
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: 
PHP Token Reflection API documentation generated by ApiGen 2.8.0