336 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			PHP
		
	
	
		
		
			
		
	
	
			336 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			PHP
		
	
	
| 
								 | 
							
								<?php declare(strict_types=1);
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								/**
							 | 
						|||
| 
								 | 
							
								 * @license Apache 2.0
							 | 
						|||
| 
								 | 
							
								 */
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								namespace OpenApi;
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								/**
							 | 
						|||
| 
								 | 
							
								 * Context.
							 | 
						|||
| 
								 | 
							
								 *
							 | 
						|||
| 
								 | 
							
								 * The context in which the annotation is parsed.
							 | 
						|||
| 
								 | 
							
								 * It includes useful metadata which the Processors can use to augment the annotations.
							 | 
						|||
| 
								 | 
							
								 *
							 | 
						|||
| 
								 | 
							
								 * Context hierarchy:
							 | 
						|||
| 
								 | 
							
								 * - parseContext
							 | 
						|||
| 
								 | 
							
								 *   |- docBlockContext
							 | 
						|||
| 
								 | 
							
								 *   |- classContext
							 | 
						|||
| 
								 | 
							
								 *      |- docBlockContext
							 | 
						|||
| 
								 | 
							
								 *      |- propertyContext
							 | 
						|||
| 
								 | 
							
								 *      |- methodContext
							 | 
						|||
| 
								 | 
							
								 *
							 | 
						|||
| 
								 | 
							
								 * @property string                           $comment     The PHP DocComment
							 | 
						|||
| 
								 | 
							
								 * @property string                           $filename
							 | 
						|||
| 
								 | 
							
								 * @property int                              $line
							 | 
						|||
| 
								 | 
							
								 * @property int                              $character
							 | 
						|||
| 
								 | 
							
								 * @property string                           $namespace
							 | 
						|||
| 
								 | 
							
								 * @property array                            $uses
							 | 
						|||
| 
								 | 
							
								 * @property string                           $class
							 | 
						|||
| 
								 | 
							
								 * @property array|string                     $extends     Interfaces may extend a list of interfaces
							 | 
						|||
| 
								 | 
							
								 * @property array                            $implements
							 | 
						|||
| 
								 | 
							
								 * @property string                           $method
							 | 
						|||
| 
								 | 
							
								 * @property string                           $property
							 | 
						|||
| 
								 | 
							
								 * @property string                           $type
							 | 
						|||
| 
								 | 
							
								 * @property string                           $trait
							 | 
						|||
| 
								 | 
							
								 * @property string                           $interface
							 | 
						|||
| 
								 | 
							
								 * @property bool                             $static      Indicate a static method
							 | 
						|||
| 
								 | 
							
								 * @property bool                             $nullable    Indicate a nullable value
							 | 
						|||
| 
								 | 
							
								 * @property bool                             $generated   Indicate the context was generated by a processor
							 | 
						|||
| 
								 | 
							
								 * @property Annotations\AbstractAnnotation   $nested
							 | 
						|||
| 
								 | 
							
								 * @property Annotations\AbstractAnnotation[] $annotations
							 | 
						|||
| 
								 | 
							
								 */
							 | 
						|||
| 
								 | 
							
								class Context
							 | 
						|||
| 
								 | 
							
								{
							 | 
						|||
| 
								 | 
							
								    /**
							 | 
						|||
| 
								 | 
							
								     * Prototypical inheritance for properties.
							 | 
						|||
| 
								 | 
							
								     *
							 | 
						|||
| 
								 | 
							
								     * @var Context
							 | 
						|||
| 
								 | 
							
								     */
							 | 
						|||
| 
								 | 
							
								    private $_parent;
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    /**
							 | 
						|||
| 
								 | 
							
								     * @param array   $properties new properties for this context
							 | 
						|||
| 
								 | 
							
								     * @param Context $parent     The parent context
							 | 
						|||
| 
								 | 
							
								     */
							 | 
						|||
| 
								 | 
							
								    public function __construct(array $properties = [], ?Context $parent = null)
							 | 
						|||
| 
								 | 
							
								    {
							 | 
						|||
| 
								 | 
							
								        foreach ($properties as $property => $value) {
							 | 
						|||
| 
								 | 
							
								            $this->$property = $value;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								        $this->_parent = $parent;
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    /**
							 | 
						|||
| 
								 | 
							
								     * Check if a property is set directly on this context and not its parent context.
							 | 
						|||
| 
								 | 
							
								     *
							 | 
						|||
| 
								 | 
							
								     * @param string $type Example: $c->is('method') or $c->is('class')
							 | 
						|||
| 
								 | 
							
								     */
							 | 
						|||
| 
								 | 
							
								    public function is(string $type): bool
							 | 
						|||
| 
								 | 
							
								    {
							 | 
						|||
| 
								 | 
							
								        return property_exists($this, $type);
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    /**
							 | 
						|||
| 
								 | 
							
								     * Check if a property is NOT set directly on this context and but its parent context.
							 | 
						|||
| 
								 | 
							
								     *
							 | 
						|||
| 
								 | 
							
								     * @param string $type Example: $c->not('method') or $c->not('class')
							 | 
						|||
| 
								 | 
							
								     */
							 | 
						|||
| 
								 | 
							
								    public function not(string $type): bool
							 | 
						|||
| 
								 | 
							
								    {
							 | 
						|||
| 
								 | 
							
								        return property_exists($this, $type) === false;
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    /**
							 | 
						|||
| 
								 | 
							
								     * Return the context containing the specified property.
							 | 
						|||
| 
								 | 
							
								     */
							 | 
						|||
| 
								 | 
							
								    public function with(string $property): ?Context
							 | 
						|||
| 
								 | 
							
								    {
							 | 
						|||
| 
								 | 
							
								        if (property_exists($this, $property)) {
							 | 
						|||
| 
								 | 
							
								            return $this;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								        if ($this->_parent !== null) {
							 | 
						|||
| 
								 | 
							
								            return $this->_parent->with($property);
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        return null;
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    public function getRootContext(): Context
							 | 
						|||
| 
								 | 
							
								    {
							 | 
						|||
| 
								 | 
							
								        if ($this->_parent !== null) {
							 | 
						|||
| 
								 | 
							
								            return $this->_parent->getRootContext();
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        return $this;
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    /**
							 | 
						|||
| 
								 | 
							
								     * Export location for debugging.
							 | 
						|||
| 
								 | 
							
								     *
							 | 
						|||
| 
								 | 
							
								     * @return string Example: "file1.php on line 12"
							 | 
						|||
| 
								 | 
							
								     */
							 | 
						|||
| 
								 | 
							
								    public function getDebugLocation(): string
							 | 
						|||
| 
								 | 
							
								    {
							 | 
						|||
| 
								 | 
							
								        $location = '';
							 | 
						|||
| 
								 | 
							
								        if ($this->class && ($this->method || $this->property)) {
							 | 
						|||
| 
								 | 
							
								            $location .= $this->fullyQualifiedName($this->class);
							 | 
						|||
| 
								 | 
							
								            if ($this->method) {
							 | 
						|||
| 
								 | 
							
								                $location .= ($this->static ? '::' : '->') . $this->method . '()';
							 | 
						|||
| 
								 | 
							
								            } elseif ($this->property) {
							 | 
						|||
| 
								 | 
							
								                $location .= ($this->static ? '::$' : '->') . $this->property;
							 | 
						|||
| 
								 | 
							
								            }
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								        if ($this->filename) {
							 | 
						|||
| 
								 | 
							
								            if ($location !== '') {
							 | 
						|||
| 
								 | 
							
								                $location .= ' in ';
							 | 
						|||
| 
								 | 
							
								            }
							 | 
						|||
| 
								 | 
							
								            $location .= $this->filename;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								        if ($this->line) {
							 | 
						|||
| 
								 | 
							
								            if ($location !== '') {
							 | 
						|||
| 
								 | 
							
								                $location .= ' on';
							 | 
						|||
| 
								 | 
							
								            }
							 | 
						|||
| 
								 | 
							
								            $location .= ' line ' . $this->line;
							 | 
						|||
| 
								 | 
							
								            if ($this->character) {
							 | 
						|||
| 
								 | 
							
								                $location .= ':' . $this->character;
							 | 
						|||
| 
								 | 
							
								            }
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        return $location;
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    /**
							 | 
						|||
| 
								 | 
							
								     * Traverse the context tree to get the property value.
							 | 
						|||
| 
								 | 
							
								     *
							 | 
						|||
| 
								 | 
							
								     * @param string $property
							 | 
						|||
| 
								 | 
							
								     */
							 | 
						|||
| 
								 | 
							
								    public function __get($property)
							 | 
						|||
| 
								 | 
							
								    {
							 | 
						|||
| 
								 | 
							
								        if ($this->_parent !== null) {
							 | 
						|||
| 
								 | 
							
								            return $this->_parent->$property;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        return null;
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    public function __toString()
							 | 
						|||
| 
								 | 
							
								    {
							 | 
						|||
| 
								 | 
							
								        return $this->getDebugLocation();
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    public function __debugInfo()
							 | 
						|||
| 
								 | 
							
								    {
							 | 
						|||
| 
								 | 
							
								        return ['-' => $this->getDebugLocation()];
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    /**
							 | 
						|||
| 
								 | 
							
								     * A short piece of text, usually one line, providing the basic function of the associated element.
							 | 
						|||
| 
								 | 
							
								     *
							 | 
						|||
| 
								 | 
							
								     * @return string
							 | 
						|||
| 
								 | 
							
								     */
							 | 
						|||
| 
								 | 
							
								    public function phpdocSummary()
							 | 
						|||
| 
								 | 
							
								    {
							 | 
						|||
| 
								 | 
							
								        $content = $this->phpdocContent();
							 | 
						|||
| 
								 | 
							
								        if (!$content) {
							 | 
						|||
| 
								 | 
							
								            return Generator::UNDEFINED;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								        $lines = preg_split('/(\n|\r\n)/', $content);
							 | 
						|||
| 
								 | 
							
								        $summary = '';
							 | 
						|||
| 
								 | 
							
								        foreach ($lines as $line) {
							 | 
						|||
| 
								 | 
							
								            $summary .= $line . "\n";
							 | 
						|||
| 
								 | 
							
								            if ($line === '' || substr($line, -1) === '.') {
							 | 
						|||
| 
								 | 
							
								                return trim($summary);
							 | 
						|||
| 
								 | 
							
								            }
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								        $summary = trim($summary);
							 | 
						|||
| 
								 | 
							
								        if ($summary === '') {
							 | 
						|||
| 
								 | 
							
								            return Generator::UNDEFINED;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        return $summary;
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    /**
							 | 
						|||
| 
								 | 
							
								     * An optional longer piece of text providing more details on the associated element’s function. This is very useful when working with a complex element.
							 | 
						|||
| 
								 | 
							
								     *
							 | 
						|||
| 
								 | 
							
								     * @return string
							 | 
						|||
| 
								 | 
							
								     */
							 | 
						|||
| 
								 | 
							
								    public function phpdocDescription()
							 | 
						|||
| 
								 | 
							
								    {
							 | 
						|||
| 
								 | 
							
								        $summary = $this->phpdocSummary();
							 | 
						|||
| 
								 | 
							
								        if (!$summary) {
							 | 
						|||
| 
								 | 
							
								            return Generator::UNDEFINED;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								        if (false !== ($substr = substr($this->phpdocContent(), strlen($summary)))) {
							 | 
						|||
| 
								 | 
							
								            $description = trim($substr);
							 | 
						|||
| 
								 | 
							
								        } else {
							 | 
						|||
| 
								 | 
							
								            $description = '';
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								        if ($description === '') {
							 | 
						|||
| 
								 | 
							
								            return Generator::UNDEFINED;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        return $description;
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    /**
							 | 
						|||
| 
								 | 
							
								     * The text contents of the phpdoc comment (excl. tags).
							 | 
						|||
| 
								 | 
							
								     *
							 | 
						|||
| 
								 | 
							
								     * @return string
							 | 
						|||
| 
								 | 
							
								     */
							 | 
						|||
| 
								 | 
							
								    public function phpdocContent()
							 | 
						|||
| 
								 | 
							
								    {
							 | 
						|||
| 
								 | 
							
								        $comment = preg_split('/(\n|\r\n)/', (string) $this->comment);
							 | 
						|||
| 
								 | 
							
								        $comment[0] = preg_replace('/[ \t]*\\/\*\*/', '', $comment[0]); // strip '/**'
							 | 
						|||
| 
								 | 
							
								        $i = count($comment) - 1;
							 | 
						|||
| 
								 | 
							
								        $comment[$i] = preg_replace('/\*\/[ \t]*$/', '', $comment[$i]); // strip '*/'
							 | 
						|||
| 
								 | 
							
								        $lines = [];
							 | 
						|||
| 
								 | 
							
								        $append = false;
							 | 
						|||
| 
								 | 
							
								        foreach ($comment as $line) {
							 | 
						|||
| 
								 | 
							
								            $line = ltrim($line, "\t *");
							 | 
						|||
| 
								 | 
							
								            if (substr($line, 0, 1) === '@') {
							 | 
						|||
| 
								 | 
							
								                break;
							 | 
						|||
| 
								 | 
							
								            }
							 | 
						|||
| 
								 | 
							
								            if ($append) {
							 | 
						|||
| 
								 | 
							
								                $i = count($lines) - 1;
							 | 
						|||
| 
								 | 
							
								                $lines[$i] = substr($lines[$i], 0, -1) . $line;
							 | 
						|||
| 
								 | 
							
								            } else {
							 | 
						|||
| 
								 | 
							
								                $lines[] = $line;
							 | 
						|||
| 
								 | 
							
								            }
							 | 
						|||
| 
								 | 
							
								            $append = (substr($line, -1) === '\\');
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								        $description = trim(implode("\n", $lines));
							 | 
						|||
| 
								 | 
							
								        if ($description === '') {
							 | 
						|||
| 
								 | 
							
								            return Generator::UNDEFINED;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        return $description;
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    /**
							 | 
						|||
| 
								 | 
							
								     * Create a Context based on the debug_backtrace.
							 | 
						|||
| 
								 | 
							
								     *
							 | 
						|||
| 
								 | 
							
								     * @deprecated
							 | 
						|||
| 
								 | 
							
								     */
							 | 
						|||
| 
								 | 
							
								    public static function detect(int $index = 0): Context
							 | 
						|||
| 
								 | 
							
								    {
							 | 
						|||
| 
								 | 
							
								        $context = new Context();
							 | 
						|||
| 
								 | 
							
								        $backtrace = debug_backtrace();
							 | 
						|||
| 
								 | 
							
								        $position = $backtrace[$index];
							 | 
						|||
| 
								 | 
							
								        if (isset($position['file'])) {
							 | 
						|||
| 
								 | 
							
								            $context->filename = $position['file'];
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								        if (isset($position['line'])) {
							 | 
						|||
| 
								 | 
							
								            $context->line = $position['line'];
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								        $caller = isset($backtrace[$index + 1]) ? $backtrace[$index + 1] : null;
							 | 
						|||
| 
								 | 
							
								        if (isset($caller['function'])) {
							 | 
						|||
| 
								 | 
							
								            $context->method = $caller['function'];
							 | 
						|||
| 
								 | 
							
								            if (isset($caller['type']) && $caller['type'] === '::') {
							 | 
						|||
| 
								 | 
							
								                $context->static = true;
							 | 
						|||
| 
								 | 
							
								            }
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								        if (isset($caller['class'])) {
							 | 
						|||
| 
								 | 
							
								            $fqn = explode('\\', $caller['class']);
							 | 
						|||
| 
								 | 
							
								            $context->class = array_pop($fqn);
							 | 
						|||
| 
								 | 
							
								            if (count($fqn)) {
							 | 
						|||
| 
								 | 
							
								                $context->namespace = implode('\\', $fqn);
							 | 
						|||
| 
								 | 
							
								            }
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        // @todo extract namespaces and use statements
							 | 
						|||
| 
								 | 
							
								        return $context;
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    /**
							 | 
						|||
| 
								 | 
							
								     * Resolve the fully qualified name.
							 | 
						|||
| 
								 | 
							
								     *
							 | 
						|||
| 
								 | 
							
								     * @param string $source The source name (class/interface/trait)
							 | 
						|||
| 
								 | 
							
								     */
							 | 
						|||
| 
								 | 
							
								    public function fullyQualifiedName(?string $source): string
							 | 
						|||
| 
								 | 
							
								    {
							 | 
						|||
| 
								 | 
							
								        if ($source === null) {
							 | 
						|||
| 
								 | 
							
								            return '';
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        if ($this->namespace) {
							 | 
						|||
| 
								 | 
							
								            $namespace = str_replace('\\\\', '\\', '\\' . $this->namespace . '\\');
							 | 
						|||
| 
								 | 
							
								        } else {
							 | 
						|||
| 
								 | 
							
								            // global namespace
							 | 
						|||
| 
								 | 
							
								            $namespace = '\\';
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        $thisSource = $this->class ?? $this->interface ?? $this->trait;
							 | 
						|||
| 
								 | 
							
								        if ($thisSource && strcasecmp($source, $thisSource) === 0) {
							 | 
						|||
| 
								 | 
							
								            return $namespace . $thisSource;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								        $pos = strpos($source, '\\');
							 | 
						|||
| 
								 | 
							
								        if ($pos !== false) {
							 | 
						|||
| 
								 | 
							
								            if ($pos === 0) {
							 | 
						|||
| 
								 | 
							
								                // Fully qualified name (\Foo\Bar)
							 | 
						|||
| 
								 | 
							
								                return $source;
							 | 
						|||
| 
								 | 
							
								            }
							 | 
						|||
| 
								 | 
							
								            // Qualified name (Foo\Bar)
							 | 
						|||
| 
								 | 
							
								            if ($this->uses) {
							 | 
						|||
| 
								 | 
							
								                foreach ($this->uses as $alias => $aliasedNamespace) {
							 | 
						|||
| 
								 | 
							
								                    $alias .= '\\';
							 | 
						|||
| 
								 | 
							
								                    if (strcasecmp(substr($source, 0, strlen($alias)), $alias) === 0) {
							 | 
						|||
| 
								 | 
							
								                        // Aliased namespace (use \Long\Namespace as Foo)
							 | 
						|||
| 
								 | 
							
								                        return '\\' . $aliasedNamespace . substr($source, strlen($alias) - 1);
							 | 
						|||
| 
								 | 
							
								                    }
							 | 
						|||
| 
								 | 
							
								                }
							 | 
						|||
| 
								 | 
							
								            }
							 | 
						|||
| 
								 | 
							
								        } elseif ($this->uses) {
							 | 
						|||
| 
								 | 
							
								            // Unqualified name (Foo)
							 | 
						|||
| 
								 | 
							
								            foreach ($this->uses as $alias => $aliasedNamespace) {
							 | 
						|||
| 
								 | 
							
								                if (strcasecmp($alias, $source) === 0) {
							 | 
						|||
| 
								 | 
							
								                    return '\\' . $aliasedNamespace;
							 | 
						|||
| 
								 | 
							
								                }
							 | 
						|||
| 
								 | 
							
								            }
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        return $namespace . $source;
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								}
							 |