refactor: Cleanup git state - commit all staged changes

Major refactoring cleanup:
- Add new controller architecture (class-controller-*.php)
- Add new settings-v2 UI (views/settings-v2/)
- Add new CSS architecture (agentic-sidebar.css, tokens)
- Add esbuild build pipeline (scripts/build.js, package.json)
- Add composer dependencies (vendor/)
- Add frontend src directory (assets/js/src/index.jsx)
- Add documentation files
- Remove old/obsolete files (class-settings.php, old CSS)

This commits all pending changes from previous refactoring efforts.
This commit is contained in:
Dwindi Ramadhana
2026-06-17 05:27:58 +07:00
parent d3f142222c
commit 690991c526
7963 changed files with 941566 additions and 67372 deletions

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace PhpParser;
use Composer\InstalledVersions;
use function strpos;
if (strpos(InstalledVersions::getVersion('nikic/php-parser') ?? '', '4') === 0) {
/**
* Modifiers used (as a bit mask) by various flags subnodes, for example on classes, functions,
* properties and constants.
*/
final class Modifiers
{
public const PUBLIC = 1;
public const PROTECTED = 2;
public const PRIVATE = 4;
public const STATIC = 8;
public const ABSTRACT = 16;
public const FINAL = 32;
public const READONLY = 64;
public const PUBLIC_SET = 128;
public const PROTECTED_SET = 256;
public const PRIVATE_SET = 512;
public const VISIBILITY_SET_MASK = self::PUBLIC_SET | self::PROTECTED_SET | self::PRIVATE_SET;
}
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
/**
* An exception specifically originating from the Reflection component.
*
* @link http://phpdoc.org
*
* @api
*/
class Exception extends \Exception
{
}

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\File;
use InvalidArgumentException;
use Override;
use phpDocumentor\Reflection\File;
use function file_exists;
use function file_get_contents;
use function md5_file;
use function sprintf;
/**
* Represents a local file on the file system.
*/
final class LocalFile implements File
{
/**
* Path to the file.
*/
private readonly string $path;
public function __construct(string $path)
{
if (!file_exists($path)) {
throw new InvalidArgumentException(sprintf('File "%s" does not exist', $path));
}
$this->path = $path;
}
/**
* Returns the content of the file as a string.
*/
#[Override]
public function getContents(): string
{
return (string) file_get_contents($this->path);
}
/**
* Returns md5 hash of the file.
*/
#[Override]
public function md5(): string
{
return md5_file($this->path);
}
/**
* Returns a relative path to the file.
*/
#[Override]
public function path(): string
{
return $this->path;
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Metadata;
interface MetaDataContainer
{
public function addMetadata(Metadata $metadata): void;
/** @return Metadata[] */
public function getMetadata(): array;
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Metadata;
interface Metadata
{
public function key(): string;
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Middleware;
use InvalidArgumentException;
use function array_pop;
use function get_debug_type;
use function sprintf;
final class ChainFactory
{
/** @param Middleware[] $middlewareList */
public static function createExecutionChain(array $middlewareList, callable $lastCallable): callable
{
while ($middleware = array_pop($middlewareList)) {
if (!$middleware instanceof Middleware) {
throw new InvalidArgumentException(
sprintf(
'Middleware must be an instance of %s but %s was given',
Middleware::class,
get_debug_type($middleware),
),
);
}
$lastCallable = static fn ($command): object => $middleware->execute($command, $lastCallable);
}
return $lastCallable;
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Middleware;
/**
* Commands are used by Middleware
*/
interface Command
{
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Middleware;
/**
* Middleware can be uses to perform extra steps during the parsing process.
*/
interface Middleware
{
/**
* Executes this middle ware class.
*
* @param callable(Command): object $next
*/
public function execute(Command $command, callable $next): object;
}

View File

@@ -0,0 +1,178 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\NodeVisitor;
use Override;
use phpDocumentor\Reflection\Fqsen;
use PhpParser\Node;
use PhpParser\Node\Const_;
use PhpParser\Node\PropertyItem;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Enum_;
use PhpParser\Node\Stmt\EnumCase;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\PropertyProperty;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
use SplDoublyLinkedList;
use function rtrim;
final class ElementNameResolver extends NodeVisitorAbstract
{
/** @var SplDoublyLinkedList<Node\Identifier|string|null> */
private SplDoublyLinkedList $parts;
public function __construct()
{
$this->resetState('\\');
}
/**
* Resets the object to a known state before start processing.
*
* @inheritDoc
*/
#[Override]
public function beforeTraverse(array $nodes)
{
$this->resetState('\\');
return null;
}
/**
* Performs a reset of the added element when needed.
*
* @inheritDoc
*/
#[Override]
public function leaveNode(Node $node)
{
switch ($node::class) {
case Namespace_::class:
case Class_::class:
case Enum_::class:
case EnumCase::class:
case ClassMethod::class:
case Trait_::class:
case PropertyProperty::class:
case PropertyItem::class:
case Node\PropertyItem::class:
case ClassConst::class:
case Const_::class:
case Interface_::class:
case Function_::class:
if (!$this->parts->isEmpty()) {
$this->parts->pop();
}
break;
}
return null;
}
/**
* Adds fqsen property to a node when applicable.
*/
#[Override]
public function enterNode(Node $node): int|null
{
switch ($node::class) {
case Namespace_::class:
if ($node->name === null) {
break;
}
$this->resetState('\\' . $node->name . '\\');
$this->setFqsen($node);
break;
case Class_::class:
case Trait_::class:
case Interface_::class:
case Enum_::class:
if (empty($node->name)) {
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
}
$this->parts->push((string) $node->name);
$this->setFqsen($node);
break;
case Function_::class:
$this->parts->push($node->name . '()');
$this->setFqsen($node);
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
case ClassMethod::class:
$this->parts->push('::' . $node->name . '()');
$this->setFqsen($node);
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
case ClassConst::class:
$this->parts->push('::');
break;
case Const_::class:
$this->parts->push($node->name);
$this->setFqsen($node);
break;
case Node\PropertyItem::class:
case PropertyProperty::class:
$this->parts->push('::$' . $node->name);
$this->setFqsen($node);
break;
case EnumCase::class:
$this->parts->push('::' . $node->name);
$this->setFqsen($node);
break;
}
return null;
}
/**
* Resets the state of the object to an empty state.
*/
private function resetState(string|null $namespace = null): void
{
$this->parts = new SplDoublyLinkedList();
$this->parts->push($namespace);
}
/**
* Builds the name of the current node using the parts that are pushed to the parts list.
*/
private function buildName(): string
{
$name = '';
foreach ($this->parts as $part) {
$name .= $part;
}
return rtrim($name, '\\');
}
private function setFqsen(Node $node): void
{
$fqsen = new Fqsen($this->buildName());
$node->setAttribute('fqsen', $fqsen);
}
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\NodeVisitor;
use PhpParser\NodeVisitor\FirstFindingVisitor as BaseFindingVisitor;
final class FindingVisitor extends BaseFindingVisitor
{
public function __construct(callable $filterCallback)
{
parent::__construct($filterCallback);
$this->foundNode = null;
}
}

View File

@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Mixed_;
use function is_string;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Descriptor representing a single Argument of a method or function.
*
* @api
*/
final class Argument
{
/** @var Type a normalized type that should be in this Argument */
private readonly Type $type;
/**
* Initializes the object.
*/
public function __construct(
/** @var string name of the Argument */
private readonly string $name,
Type|null $type = null,
/** @var Expression|string|null the default value for an argument or null if none is provided */
private Expression|string|null $default = null,
/** @var bool whether the argument passes the parameter by reference instead of by value */
private readonly bool $byReference = false,
/** @var bool Determines if this Argument represents a variadic argument */
private readonly bool $isVariadic = false,
) {
if ($type === null) {
$type = new Mixed_();
}
if (is_string($this->default)) {
trigger_error(
'Default values for arguments should be of type Expression, support for strings will be '
. 'removed in 7.x',
E_USER_DEPRECATED,
);
$this->default = new Expression($this->default, []);
}
$this->type = $type;
}
/**
* Returns the name of this argument.
*/
public function getName(): string
{
return $this->name;
}
public function getType(): Type|null
{
return $this->type;
}
public function getDefault(bool $asString = true): Expression|string|null
{
if ($this->default === null) {
return null;
}
if ($asString) {
trigger_error(
'The Default value will become of type Expression by default',
E_USER_DEPRECATED,
);
return (string) $this->default;
}
return $this->default;
}
public function isByReference(): bool
{
return $this->byReference;
}
public function isVariadic(): bool
{
return $this->isVariadic;
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php;
/** @api */
final class AsymmetricVisibility extends Visibility
{
public function __construct(
private Visibility $readVisibility,
private Visibility $writeVisibility,
) {
parent::__construct((string) $readVisibility);
}
public function getReadVisibility(): Visibility
{
return $this->readVisibility;
}
public function getWriteVisibility(): Visibility
{
return $this->writeVisibility;
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php;
use Override;
use phpDocumentor\Reflection\Element;
use phpDocumentor\Reflection\Fqsen;
/** @api */
final class Attribute implements Element
{
/** @param CallArgument[] $arguments */
public function __construct(private readonly Fqsen $fqsen, private readonly array $arguments)
{
}
#[Override]
public function getFqsen(): Fqsen
{
return $this->fqsen;
}
/** @return CallArgument[] */
public function getArguments(): array
{
return $this->arguments;
}
#[Override]
public function getName(): string
{
return $this->fqsen->getName();
}
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php;
interface AttributeContainer
{
public function addAttribute(Attribute $attribute): void;
/** @return Attribute[] */
public function getAttributes(): array;
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php;
/**
* Represents an argument in a function or method call.
*
* @api
*/
final class CallArgument
{
public function __construct(private readonly string $value, private readonly string|null $name = null)
{
}
public function getValue(): string
{
return $this->value;
}
public function getName(): string|null
{
return $this->name;
}
}

View File

@@ -0,0 +1,237 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use Override;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Element;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
/**
* Descriptor representing a Class.
*
* @api
*/
// @codingStandardsIgnoreStart
final class Class_ implements Element, MetaDataContainerInterface, AttributeContainer
// @codingStandardsIgnoreEnd
{
use MetadataContainer;
use HasAttributes;
/** @var Fqsen[] References to interfaces that are implemented by this class. */
private array $implements = [];
/** @var Constant[] References to constants defined in this class. */
private array $constants = [];
/** @var Property[] References to properties defined in this class. */
private array $properties = [];
/** @var Method[] References to methods defined in this class. */
private array $methods = [];
/** @var Fqsen[] References to traits consumed by this class */
private array $usedTraits = [];
private readonly Location $location;
private readonly Location $endLocation;
/**
* Initializes a number of properties with the given values. Others are initialized by definition.
*/
public function __construct(
/** @var Fqsen Full Qualified Structural Element Name */
private readonly Fqsen $fqsen,
private readonly DocBlock|null $docBlock = null,
/** @var Fqsen|null The class this class is extending. */
private readonly Fqsen|null $parent = null,
/** @var bool Whether this is an abstract class. */
private readonly bool $abstract = false,
/** @var bool Whether this class is marked as final and can't be subclassed. */
private readonly bool $final = false,
Location|null $location = null,
Location|null $endLocation = null,
private readonly bool $readOnly = false,
) {
if ($location === null) {
$location = new Location(-1);
}
if ($endLocation === null) {
$endLocation = new Location(-1);
}
$this->location = $location;
$this->endLocation = $endLocation;
}
/**
* Returns true when this class is final. Otherwise returns false.
*/
public function isFinal(): bool
{
return $this->final;
}
/**
* Returns true when this class is abstract. Otherwise returns false.
*/
public function isAbstract(): bool
{
return $this->abstract;
}
/**
* Returns true when this class is read-only. Otherwise returns false.
*/
public function isReadOnly(): bool
{
return $this->readOnly;
}
/**
* Returns the superclass this class is extending if available.
*/
public function getParent(): Fqsen|null
{
return $this->parent;
}
/**
* Returns the interfaces this class is implementing.
*
* @return Fqsen[]
*/
public function getInterfaces(): array
{
return $this->implements;
}
/**
* Add a interface Fqsen this class is implementing.
*/
public function addInterface(Fqsen $interface): void
{
$this->implements[(string) $interface] = $interface;
}
/**
* Returns the constants of this class.
*
* @return Constant[]
*/
public function getConstants(): array
{
return $this->constants;
}
/**
* Add Constant to this class.
*/
public function addConstant(Constant $constant): void
{
$this->constants[(string) $constant->getFqsen()] = $constant;
}
/**
* Returns the methods of this class.
*
* @return Method[]
*/
public function getMethods(): array
{
return $this->methods;
}
/**
* Add a method to this class.
*/
public function addMethod(Method $method): void
{
$this->methods[(string) $method->getFqsen()] = $method;
}
/**
* Returns the properties of this class.
*
* @return Property[]
*/
public function getProperties(): array
{
return $this->properties;
}
/**
* Add a property to this class.
*/
public function addProperty(Property $property): void
{
$this->properties[(string) $property->getFqsen()] = $property;
}
/**
* Returns the traits used by this class.
*
* @return Fqsen[]
*/
public function getUsedTraits(): array
{
return $this->usedTraits;
}
/**
* Add trait fqsen used by this class.
*/
public function addUsedTrait(Fqsen $fqsen): void
{
$this->usedTraits[(string) $fqsen] = $fqsen;
}
/**
* Returns the Fqsen of the element.
*/
#[Override]
public function getFqsen(): Fqsen
{
return $this->fqsen;
}
/**
* Returns the name of the element.
*/
#[Override]
public function getName(): string
{
return $this->fqsen->getName();
}
public function getDocBlock(): DocBlock|null
{
return $this->docBlock;
}
public function getLocation(): Location
{
return $this->location;
}
public function getEndLocation(): Location
{
return $this->endLocation;
}
}

View File

@@ -0,0 +1,145 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use Override;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Element;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use phpDocumentor\Reflection\Type;
use function is_string;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Descriptor representing a constant
*
* @api
*/
final class Constant implements Element, MetaDataContainerInterface, AttributeContainer
{
use MetadataContainer;
use HasAttributes;
private readonly Location $location;
private readonly Location $endLocation;
private readonly Visibility $visibility;
/**
* Initializes the object.
*/
public function __construct(
private readonly Fqsen $fqsen,
private readonly DocBlock|null $docBlock = null,
private Expression|string|null $value = null,
Location|null $location = null,
Location|null $endLocation = null,
Visibility|null $visibility = null,
private readonly bool $final = false,
private readonly Type|null $type = null,
) {
$this->location = $location ?: new Location(-1);
$this->endLocation = $endLocation ?: new Location(-1);
$this->visibility = $visibility ?: new Visibility(Visibility::PUBLIC_);
if (!is_string($this->value)) {
return;
}
trigger_error(
'Constant values should be of type Expression, support for strings will be '
. 'removed in 6.x',
E_USER_DEPRECATED,
);
$this->value = new Expression($this->value, []);
}
/**
* Returns the expression value for this constant.
*/
public function getValue(bool $asString = true): Expression|string|null
{
if ($this->value === null) {
return null;
}
if ($asString) {
trigger_error(
'The expression value will become of type Expression by default',
E_USER_DEPRECATED,
);
return (string) $this->value;
}
return $this->value;
}
/**
* Returns the Fqsen of the element.
*/
#[Override]
public function getFqsen(): Fqsen
{
return $this->fqsen;
}
/**
* Returns the name of the element.
*/
#[Override]
public function getName(): string
{
return $this->fqsen->getName();
}
/**
* Returns DocBlock of this constant if available.
*/
public function getDocBlock(): DocBlock|null
{
return $this->docBlock;
}
public function getLocation(): Location
{
return $this->location;
}
public function getEndLocation(): Location
{
return $this->endLocation;
}
public function getVisibility(): Visibility
{
return $this->visibility;
}
public function isFinal(): bool
{
return $this->final;
}
public function getType(): Type|null
{
return $this->type;
}
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php;
use Override;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Element;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use function is_string;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Represents a case in an Enum.
*
* @api
*/
final class EnumCase implements Element, MetaDataContainerInterface, AttributeContainer
{
use MetadataContainer;
use HasAttributes;
private readonly Location $location;
private readonly Location $endLocation;
public function __construct(
private readonly Fqsen $fqsen,
private readonly DocBlock|null $docBlock,
Location|null $location = null,
Location|null $endLocation = null,
private Expression|string|null $value = null,
) {
if ($location === null) {
$location = new Location(-1);
}
if ($endLocation === null) {
$endLocation = new Location(-1);
}
$this->location = $location;
$this->endLocation = $endLocation;
if (!is_string($this->value)) {
return;
}
trigger_error(
'Expression values for enum cases should be of type Expression, support for strings will be '
. 'removed in 7.x',
E_USER_DEPRECATED,
);
$this->value = new Expression($this->value, []);
}
#[Override]
public function getFqsen(): Fqsen
{
return $this->fqsen;
}
#[Override]
public function getName(): string
{
return $this->fqsen->getName();
}
public function getDocBlock(): DocBlock|null
{
return $this->docBlock;
}
public function getLocation(): Location
{
return $this->location;
}
public function getEndLocation(): Location
{
return $this->endLocation;
}
/**
* Returns the value for this enum case.
*/
public function getValue(bool $asString = true): Expression|string|null
{
if ($this->value === null) {
return null;
}
if ($asString) {
trigger_error(
'The enum case value will become of type Expression by default',
E_USER_DEPRECATED,
);
return (string) $this->value;
}
return $this->value;
}
}

View File

@@ -0,0 +1,187 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use Override;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Element;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use phpDocumentor\Reflection\Type;
/**
* Descriptor representing an Enum.
*
* @api
*/
final class Enum_ implements Element, MetaDataContainerInterface, AttributeContainer
{
use MetadataContainer;
use HasAttributes;
private readonly Location $location;
private readonly Location $endLocation;
/** @var EnumCase[] */
private array $cases = [];
/** @var array<string, Fqsen> */
private array $implements = [];
/** @var Constant[] References to constants defined in this enum. */
private array $constants = [];
/** @var array<string, Method> */
private array $methods = [];
/** @var array<string, Fqsen> */
private array $usedTraits = [];
public function __construct(
/** @var Fqsen Full Qualified Structural Element Name */
private readonly Fqsen $fqsen,
private readonly Type|null $backedType,
private readonly DocBlock|null $docBlock = null,
Location|null $location = null,
Location|null $endLocation = null,
) {
if ($location === null) {
$location = new Location(-1);
}
if ($endLocation === null) {
$endLocation = new Location(-1);
}
$this->location = $location;
$this->endLocation = $endLocation;
}
#[Override]
public function getFqsen(): Fqsen
{
return $this->fqsen;
}
#[Override]
public function getName(): string
{
return $this->fqsen->getName();
}
public function getDocBlock(): DocBlock|null
{
return $this->docBlock;
}
public function getLocation(): Location
{
return $this->location;
}
public function getEndLocation(): Location
{
return $this->endLocation;
}
public function addCase(EnumCase $case): void
{
$this->cases[(string) $case->getFqsen()] = $case;
}
/** @return EnumCase[] */
public function getCases(): array
{
return $this->cases;
}
/**
* Returns the interfaces this enum is implementing.
*
* @return Fqsen[]
*/
public function getInterfaces(): array
{
return $this->implements;
}
/**
* Add an interface Fqsen this enum is implementing.
*/
public function addInterface(Fqsen $interface): void
{
$this->implements[(string) $interface] = $interface;
}
/**
* Returns the constants of this enum.
*
* @return Constant[]
*/
public function getConstants(): array
{
return $this->constants;
}
/**
* Add Constant to this enum.
*/
public function addConstant(Constant $constant): void
{
$this->constants[(string) $constant->getFqsen()] = $constant;
}
/**
* Returns the methods of this enum.
*
* @return Method[]
*/
public function getMethods(): array
{
return $this->methods;
}
/**
* Add a method to this enum.
*/
public function addMethod(Method $method): void
{
$this->methods[(string) $method->getFqsen()] = $method;
}
/**
* Returns the traits used by this enum.
*
* @return Fqsen[]
*/
public function getUsedTraits(): array
{
return $this->usedTraits;
}
/**
* Add trait fqsen used by this enum.
*/
public function addUsedTrait(Fqsen $fqsen): void
{
$this->usedTraits[(string) $fqsen] = $fqsen;
}
public function getBackedType(): Type|null
{
return $this->backedType;
}
}

View File

@@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter;
use phpDocumentor\Reflection\Type;
use Webmozart\Assert\Assert;
use function array_keys;
use function md5;
use function str_replace;
/**
* Represents an expression with a define statement, constant, property, enum case and any other location.
*
* Certain expressions contain useful references to other elements or types. Examples of these are:
*
* - Define statements that use an expression to refer to a class or function
* - Properties whose default value refers to a constant
* - Arguments whose default value initialize an object
* - Enum Cases that refer to a function or constant
*
* This class represents every location where an expression is used and contains 2 pieces of information:
*
* - The expression string containing placeholders linking to useful information
* - An array of 'parts' whose keys equal the placeholders in the expression string and whose values is the extracted
* information, such as an {@see FQSEN} or {@see Type}.
*
* In a way, the expression string is similar in nature to a URI Template (see links) where you have a string containing
* variables that can be replaced. These variables are delimited by `{{` and `}}`, and are build up of the prefix PHPDOC
* and then an MD5 hash of the name of the extracted information.
*
* It is not necessary for a consumer to interpret the information when they do not need it, a {@see self::__toString()}
* magic method is provided that will replace the placeholders with the `toString()` output of each part.
*
* @link https://github.com/php/php-langspec/blob/master/spec/10-expressions.md
* for the definition of expressions in PHP.
* @link https://www.rfc-editor.org/rfc/rfc6570 for more information on URI Templates.
* @see ExpressionPrinter how an expression coming from PHP-Parser is transformed into an expression.
*
* @api
*/
final class Expression
{
/** @var string The expression string containing placeholders for any extracted Types or FQSENs. */
private string $expression;
/**
* The collection of placeholders with the value that their holding.
*
* In the expression string there can be several placeholders, this array contains a placeholder => value pair
* that can be used by consumers to map the data to another formatting, adding links for example, and then render
* the expression.
*
* @var array<string, Fqsen|Type>
*/
private array $parts;
/**
* Returns the recommended placeholder string format given a name.
*
* Consumers can use their own formats when needed, the placeholders are all keys in the {@see self::$parts} array
* and not interpreted by this class. However, to prevent collisions it is recommended to use this method to
* generate a placeholder.
*
* @param string $name a string identifying the element for which the placeholder is generated.
*/
public static function generatePlaceholder(string $name): string
{
Assert::notEmpty($name);
return '{{ PHPDOC' . md5($name) . ' }}';
}
/** @param array<string, Fqsen|Type> $parts */
public function __construct(string $expression, array $parts = [])
{
Assert::stringNotEmpty($expression);
Assert::allIsInstanceOfAny($parts, [Fqsen::class, Type::class]);
$this->expression = $expression;
$this->parts = $parts;
}
/**
* The raw expression string containing placeholders for any extracted Types or FQSENs.
*
* @see self::render() to render a human-readable expression and to replace some parts with custom values.
* @see self::__toString() to render a human-readable expression with the previously extracted parts.
*/
public function getExpression(): string
{
return $this->expression;
}
/**
* A list of extracted parts for which placeholders exist in the expression string.
*
* The returned array will have the placeholders of the expression string as keys, and the related FQSEN or Type as
* value. This can be used as a basis for doing your own transformations to {@see self::render()} the expression
* in a custom way; or to extract type information from an expression and use that elsewhere in your application.
*
* @see ExpressionPrinter to transform a PHP-Parser expression into an expression string and list of parts.
*
* @return array<string, Fqsen|Type>
*/
public function getParts(): array
{
return $this->parts;
}
/**
* Renders the expression as a string and replaces all placeholders with either a provided value, or the
* stringified value from the parts in this expression.
*
* The keys of the replacement parts should match those of {@see self::getParts()}, any unrecognized key is not
* handled.
*
* @param array<string, string> $replacementParts
*/
public function render(array $replacementParts = []): string
{
Assert::allStringNotEmpty($replacementParts);
$valuesAsStrings = [];
foreach ($this->parts as $placeholder => $part) {
$valuesAsStrings[$placeholder] = $replacementParts[$placeholder] ?? (string) $part;
}
return str_replace(array_keys($this->parts), $valuesAsStrings, $this->expression);
}
/**
* Returns a rendered version of the expression string where all placeholders are replaced by the stringified
* versions of the included parts.
*
* @see self::$parts for the list of parts used in rendering
* @see self::render() to influence rendering of the expression.
*/
public function __toString(): string
{
return $this->render();
}
}

View File

@@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Expression;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Php\Expression;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\Object_;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\PrettyPrinter\Standard;
use function ltrim;
final class ExpressionPrinter extends Standard
{
/** @var array<string, Fqsen|Type> */
private array $parts = [];
private Context|null $context = null;
private TypeResolver $typeResolver;
/** {@inheritDoc} */
public function __construct(array $options = [])
{
parent::__construct($options);
$this->typeResolver = new TypeResolver(
new FqsenResolver(),
);
}
protected function resetState(): void
{
parent::resetState();
$this->parts = [];
}
public function prettyPrintExpr(Expr $node, Context|null $context = null): string
{
$this->context = $context;
return parent::prettyPrintExpr($node);
}
protected function pName(Name $node): string
{
$renderedName = $this->typeResolver->resolve(parent::pName($node), $this->context);
$placeholder = Expression::generatePlaceholder((string) $renderedName);
if ($renderedName instanceof Object_ && $renderedName->getFqsen() !== null) {
$this->parts[$placeholder] = $renderedName->getFqsen();
} else {
$this->parts[$placeholder] = $renderedName;
}
return $placeholder;
}
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
protected function pName_FullyQualified(Name\FullyQualified $node): string
{
$renderedName = parent::pName_FullyQualified($node);
$placeholder = Expression::generatePlaceholder($renderedName);
$this->parts[$placeholder] = new Fqsen($renderedName);
return $placeholder;
}
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
protected function pExpr_ClassConstFetch(Expr\ClassConstFetch $node): string
{
$renderedName = parent::pObjectProperty($node->name);
if ($node->class instanceof Name\FullyQualified) {
$className = parent::pName_FullyQualified($node->class);
$className = $this->typeResolver->resolve($className, $this->context);
} elseif ($node->class instanceof Name) {
$className = parent::pName($node->class);
$className = $this->typeResolver->resolve($className, $this->context);
} else {
$className = $this->p($node->class);
}
$placeholder = Expression::generatePlaceholder((string) $renderedName);
$this->parts[$placeholder] = new Fqsen(
'\\' . ltrim((string) $className, '\\') . '::' . $renderedName,
);
return $placeholder;
}
/** @return array<string, Fqsen|Type> */
public function getParts(): array
{
return $this->parts;
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use InvalidArgumentException;
use Override;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Types\Context;
use PhpParser\Comment\Doc;
use PhpParser\NodeAbstract;
use function get_debug_type;
use function sprintf;
abstract class AbstractFactory implements ProjectFactoryStrategy
{
/** @param iterable<Reducer> $reducers */
public function __construct(
protected readonly DocBlockFactoryInterface $docBlockFactory,
protected readonly iterable $reducers = [],
) {
}
/**
* Returns true when the strategy is able to handle the object.
*
* @param object $object object to check.
*/
#[Override]
abstract public function matches(ContextStack $context, object $object): bool;
#[Override]
public function create(ContextStack $context, object $object, StrategyContainer $strategies): void
{
if (!$this->matches($context, $object)) {
throw new InvalidArgumentException(
sprintf(
'%s cannot handle objects with the type %s',
self::class,
get_debug_type($object),
),
);
}
$element = $this->doCreate($context, $object, $strategies);
foreach ($this->reducers as $reducer) {
$element = $reducer->reduce($context, $object, $strategies, $element);
}
}
/**
* Creates an Element out of the given object.
*
* Since an object might contain other objects that need to be converted the $factory is passed so it can be
* used to create nested Elements.
*
* @param NodeAbstract|object $object object to convert to an Element
*/
abstract protected function doCreate(ContextStack $context, object $object, StrategyContainer $strategies): object|null;
protected function createDocBlock(Doc|null $docBlock = null, Context|null $context = null): DocBlock|null
{
if ($docBlock === null) {
return null;
}
return $this->docBlockFactory->create($docBlock->getText(), $context);
}
}

View File

@@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Override;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\Class_;
use phpDocumentor\Reflection\Php\Constant as ConstantElement;
use phpDocumentor\Reflection\Php\Enum_;
use phpDocumentor\Reflection\Php\Expression;
use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter;
use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer;
use phpDocumentor\Reflection\Php\Interface_;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Php\Trait_;
use phpDocumentor\Reflection\Php\Visibility;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use Webmozart\Assert\Assert;
use function is_string;
/**
* Strategy to convert ClassConstantIterator to ConstantElement
*
* @see ConstantElement
* @see ClassConstantIterator
*/
final class ClassConstant extends AbstractFactory
{
/** @param iterable<Reducer> $reducers */
public function __construct(
DocBlockFactoryInterface $blockFactory,
private readonly PrettyPrinter $valueConverter,
iterable $reducers = [],
) {
parent::__construct($blockFactory, $reducers);
}
#[Override]
public function matches(ContextStack $context, object $object): bool
{
return $object instanceof ClassConst;
}
/**
* Creates an Constant out of the given object.
*
* Since an object might contain other objects that need to be converted the $factory is passed so it can be
* used to create nested Elements.
*
* @param ContextStack $context of the created object
* @param ClassConst $object object to convert to an Element
* @param StrategyContainer $strategies used to convert nested objects.
*/
#[Override]
protected function doCreate(
ContextStack $context,
object $object,
StrategyContainer $strategies,
): object|null {
$constantContainer = $context->peek();
Assert::isInstanceOfAny(
$constantContainer,
[
Class_::class,
Enum_::class,
Interface_::class,
Trait_::class,
],
);
$constants = new ClassConstantIterator($object);
foreach ($constants as $const) {
$constant = new ConstantElement(
$const->getFqsen(),
$this->createDocBlock($const->getDocComment(), $context->getTypeContext()),
$this->determineValue($const),
new Location($const->getLine()),
new Location($const->getEndLine()),
$this->buildVisibility($const),
$const->isFinal(),
(new Type())->fromPhpParser($const->getType(), $context->getTypeContext()),
);
foreach ($this->reducers as $reducer) {
$constant = $reducer->reduce($context, $const, $strategies, $constant);
}
if ($constant === null) {
continue;
}
$constantContainer->addConstant($constant);
}
return null;
}
private function determineValue(ClassConstantIterator $value): Expression
{
$expression = $this->valueConverter->prettyPrintExpr($value->getValue());
if ($this->valueConverter instanceof ExpressionPrinter) {
$expression = new Expression($expression, $this->valueConverter->getParts());
}
if (is_string($expression)) {
$expression = new Expression($expression, []);
}
return $expression;
}
/**
* Converts the visibility of the constant to a valid Visibility object.
*/
private function buildVisibility(ClassConstantIterator $node): Visibility
{
if ($node->isPrivate()) {
return new Visibility(Visibility::PRIVATE_);
}
if ($node->isProtected()) {
return new Visibility(Visibility::PROTECTED_);
}
return new Visibility(Visibility::PUBLIC_);
}
}

View File

@@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Iterator;
use Override;
use phpDocumentor\Reflection\Fqsen;
use PhpParser\Comment\Doc;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\ClassConst;
/**
* This class acts like a combination of a ClassConst and Const_
* to be able to create constant descriptors using a normal strategy.
*
* @implements Iterator<int, ClassConstantIterator>
*/
final class ClassConstantIterator implements Iterator
{
/** @var int index of the current ClassConst to use */
private int $index = 0;
/**
* Initializes the class with source data.
*/
public function __construct(private readonly ClassConst $classConstants)
{
}
/**
* Gets line the node started in.
*
* @return int Line
*/
public function getLine(): int
{
return $this->classConstants->getLine();
}
/**
* Gets line the node ended in.
*
* @return int Line
*/
public function getEndLine(): int
{
return $this->classConstants->getEndLine();
}
/**
* Returns the name of the current constant.
*/
public function getName(): string
{
return (string) $this->classConstants->consts[$this->index]->name;
}
/**
* Returns the fqsen of the current constant.
*/
public function getFqsen(): Fqsen
{
return $this->classConstants->consts[$this->index]->getAttribute('fqsen');
}
/**
* returns true when the current property is public.
*/
public function isPublic(): bool
{
return $this->classConstants->isPublic();
}
/**
* returns true when the current property is protected.
*/
public function isProtected(): bool
{
return $this->classConstants->isProtected();
}
/**
* returns true when the current property is private.
*/
public function isPrivate(): bool
{
return $this->classConstants->isPrivate();
}
/**
* Gets the doc comment of the node.
*
* The doc comment has to be the last comment associated with the node.
*/
public function getDocComment(): Doc|null
{
$docComment = $this->classConstants->consts[$this->index]->getDocComment();
if ($docComment === null) {
$docComment = $this->classConstants->getDocComment();
}
return $docComment;
}
public function getValue(): Expr
{
return $this->classConstants->consts[$this->index]->value;
}
public function isFinal(): bool
{
return $this->classConstants->isFinal();
}
/**
* Gets the type of the constant.
*/
public function getType(): Identifier|Name|ComplexType|null
{
return $this->classConstants->type;
}
/** @link http://php.net/manual/en/iterator.current.php */
#[Override]
public function current(): self
{
return $this;
}
/** @link http://php.net/manual/en/iterator.next.php */
#[Override]
public function next(): void
{
++$this->index;
}
/** @link http://php.net/manual/en/iterator.key.php */
#[Override]
public function key(): int|null
{
return $this->index;
}
/** @link http://php.net/manual/en/iterator.valid.php */
#[Override]
public function valid(): bool
{
return isset($this->classConstants->consts[$this->index]);
}
/** @link http://php.net/manual/en/iterator.rewind.php */
#[Override]
public function rewind(): void
{
$this->index = 0;
}
}

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Override;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\Class_ as ClassElement;
use phpDocumentor\Reflection\Php\File as FileElement;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Node\Stmt\Class_ as ClassNode;
use function assert;
/**
* Strategy to create a ClassElement including all sub elements.
*/
final class Class_ extends AbstractFactory
{
#[Override]
public function matches(ContextStack $context, object $object): bool
{
return $object instanceof ClassNode;
}
/**
* Creates an ClassElement out of the given object.
*
* Since an object might contain other objects that need to be converted the $factory is passed so it can be
* used to create nested Elements.
*
* @param ContextStack $context of the created object
* @param ClassNode $object
*/
#[Override]
protected function doCreate(ContextStack $context, object $object, StrategyContainer $strategies): object|null
{
$docBlock = $this->createDocBlock($object->getDocComment(), $context->getTypeContext());
$classElement = new ClassElement(
$object->getAttribute('fqsen'),
$docBlock,
isset($object->extends) ? new Fqsen('\\' . $object->extends) : null,
$object->isAbstract(),
$object->isFinal(),
new Location($object->getLine()),
new Location($object->getEndLine()),
$object->isReadonly(),
);
foreach ($object->implements as $interfaceClassName) {
$classElement->addInterface(
new Fqsen('\\' . $interfaceClassName->toString()),
);
}
$file = $context->peek();
assert($file instanceof FileElement);
$file->addClass($classElement);
foreach ($object->stmts as $stmt) {
$thisContext = $context->push($classElement);
$strategy = $strategies->findMatching($thisContext, $stmt);
$strategy->create($thisContext, $stmt, $strategies);
}
return $classElement;
}
}

View File

@@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory;
use OutOfBoundsException;
use Override;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\Class_ as ClassElement;
use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Modifiers;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use Webmozart\Assert\Assert;
final class ConstructorPromotion extends AbstractFactory
{
/** @param iterable<Reducer> $reducers */
public function __construct(
private readonly ProjectFactoryStrategy $methodStrategy,
DocBlockFactoryInterface $docBlockFactory,
private readonly PrettyPrinter $valueConverter,
iterable $reducers = [],
) {
parent::__construct($docBlockFactory, $reducers);
}
#[Override]
public function matches(ContextStack $context, object $object): bool
{
try {
return $context->peek() instanceof ClassElement &&
$object instanceof ClassMethod &&
(string) ($object->name) === '__construct';
} catch (OutOfBoundsException) {
return false;
}
}
/** @param ClassMethod $object */
#[Override]
protected function doCreate(ContextStack $context, object $object, StrategyContainer $strategies): object|null
{
$this->methodStrategy->create($context, $object, $strategies);
foreach ($object->params as $param) {
if ($param->flags === 0) {
continue;
}
$this->promoteParameterToProperty($context, $strategies, $param);
}
return $context->peek();
}
private function promoteParameterToProperty(ContextStack $context, StrategyContainer $strategies, Param $param): void
{
$methodContainer = $context->peek();
Assert::isInstanceOf($methodContainer, ClassElement::class);
Assert::isInstanceOf($param->var, Variable::class);
$property = PropertyBuilder::create(
$this->valueConverter,
$this->docBlockFactory,
$strategies,
$this->reducers,
)->fqsen(new Fqsen($methodContainer->getFqsen() . '::$' . (string) $param->var->name))
->visibility($param)
->type($param->type)
->docblock($param->getDocComment())
->default($param->default)
->readOnly($this->readOnly($param->flags))
->static(false)
->startLocation(new Location($param->getLine()))
->endLocation(new Location($param->getEndLine()))
->hooks($param->hooks ?? [])
->build($context);
foreach ($this->reducers as $reducer) {
$property = $reducer->reduce($context, $param, $strategies, $property);
}
if ($property === null) {
return;
}
$methodContainer->addProperty($property);
}
private function readOnly(int $flags): bool
{
return (bool) ($flags & Modifiers::READONLY) === true;
}
}

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory;
use OutOfBoundsException;
use phpDocumentor\Reflection\Element;
use phpDocumentor\Reflection\Php\File as FileElement;
use phpDocumentor\Reflection\Php\Project;
use phpDocumentor\Reflection\Php\PropertyHook;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use function array_reverse;
use function end;
final class ContextStack
{
/** @var (Element|FileElement|PropertyHook)[] */
private array $elements = [];
public function __construct(private readonly Project $project, private readonly TypeContext|null $typeContext = null)
{
}
/** @param (Element|FileElement|PropertyHook)[] $elements */
private static function createFromSelf(Project $project, TypeContext|null $typeContext, array $elements): self
{
$self = new self($project, $typeContext);
$self->elements = $elements;
return $self;
}
public function push(Element|FileElement|PropertyHook $element): self
{
$elements = $this->elements;
$elements[] = $element;
return self::createFromSelf($this->project, $this->typeContext, $elements);
}
public function withTypeContext(TypeContext $typeContext): ContextStack
{
return self::createFromSelf($this->project, $typeContext, $this->elements);
}
public function getTypeContext(): TypeContext|null
{
return $this->typeContext;
}
public function getProject(): Project
{
return $this->project;
}
public function peek(): Element|FileElement|PropertyHook
{
$element = end($this->elements);
if ($element === false) {
throw new OutOfBoundsException('Stack is empty');
}
return $element;
}
/**
* Returns the first element of type.
*
* Will reverse search the stack for an element matching $type. Will return null when the element type is not
* in the current stack.
*
* @param class-string $type
*/
public function search(string $type): Element|FileElement|PropertyHook|null
{
$reverseElements = array_reverse($this->elements);
foreach ($reverseElements as $element) {
if ($element instanceof $type) {
return $element;
}
}
return null;
}
}

View File

@@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Override;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\Constant as ConstantElement;
use phpDocumentor\Reflection\Php\Expression as ValueExpression;
use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter;
use phpDocumentor\Reflection\Php\File as FileElement;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Php\ValueEvaluator\ConstantEvaluator;
use PhpParser\ConstExprEvaluationException;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\VariadicPlaceholder;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use function assert;
use function is_string;
use function sprintf;
use function str_starts_with;
/**
* Strategy to convert `define` expressions to ConstantElement
*
* @see ConstantElement
* @see GlobalConstantIterator
*/
final class Define extends AbstractFactory
{
/**
* Initializes the object.
*/
public function __construct(
DocBlockFactoryInterface $docBlockFactory,
private readonly PrettyPrinter $valueConverter,
private readonly ConstantEvaluator $constantEvaluator = new ConstantEvaluator(),
) {
parent::__construct($docBlockFactory);
}
#[Override]
public function matches(ContextStack $context, object $object): bool
{
if (!$object instanceof Expression) {
return false;
}
$expression = $object->expr;
if (!$expression instanceof FuncCall) {
return false;
}
if (!$expression->name instanceof Name) {
return false;
}
return (string) $expression->name === 'define';
}
/**
* Creates an Constant out of the given object.
*
* Since an object might contain other objects that need to be converted the $factory is passed so it can be
* used to create nested Elements.
*
* @param Expression $object object to convert to an Element
* @param StrategyContainer $strategies used to convert nested objects.
*/
#[Override]
protected function doCreate(
ContextStack $context,
object $object,
StrategyContainer $strategies,
): object|null {
$expression = $object->expr;
assert($expression instanceof FuncCall);
[$name, $value] = $expression->args;
//We cannot calculate the name of a variadic consuming define.
if ($name instanceof VariadicPlaceholder || $value instanceof VariadicPlaceholder) {
return null;
}
$file = $context->search(FileElement::class);
assert($file instanceof FileElement);
$fqsen = $this->determineFqsen($name, $context);
if ($fqsen === null) {
return null;
}
$constant = new ConstantElement(
$fqsen,
$this->createDocBlock($object->getDocComment(), $context->getTypeContext()),
$this->determineValue($value),
new Location($object->getLine()),
new Location($object->getEndLine()),
);
$file->addConstant($constant);
return $constant;
}
private function determineValue(Arg|null $value): ValueExpression|null
{
if ($value === null) {
return null;
}
$expression = $this->valueConverter->prettyPrintExpr($value->value);
if ($this->valueConverter instanceof ExpressionPrinter) {
$expression = new ValueExpression($expression, $this->valueConverter->getParts());
}
if (is_string($expression)) {
$expression = new ValueExpression($expression, []);
}
return $expression;
}
private function determineFqsen(Arg $name, ContextStack $context): Fqsen|null
{
return $this->fqsenFromExpression($name->value, $context);
}
private function fqsenFromExpression(Expr $nameString, ContextStack $context): Fqsen|null
{
try {
return $this->fqsenFromString($this->constantEvaluator->evaluate($nameString, $context));
} catch (ConstExprEvaluationException) {
//Ignore any errors as we cannot evaluate all expressions
return null;
}
}
private function fqsenFromString(string $nameString): Fqsen
{
if (str_starts_with($nameString, '\\') === false) {
return new Fqsen(sprintf('\\%s', $nameString));
}
return new Fqsen(sprintf('%s', $nameString));
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory;
use Override;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\Enum_ as EnumElement;
use phpDocumentor\Reflection\Php\EnumCase as EnumCaseElement;
use phpDocumentor\Reflection\Php\Expression as ValueExpression;
use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter;
use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Node\Stmt\EnumCase as EnumCaseNode;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use function assert;
use function is_string;
final class EnumCase extends AbstractFactory
{
/** @param iterable<Reducer> $reducers */
public function __construct(
DocBlockFactoryInterface $docBlockFactory,
private readonly PrettyPrinter $prettyPrinter,
iterable $reducers = [],
) {
parent::__construct($docBlockFactory, $reducers);
}
#[Override]
public function matches(ContextStack $context, object $object): bool
{
return $object instanceof EnumCaseNode;
}
/** @param EnumCaseNode $object */
#[Override]
protected function doCreate(ContextStack $context, object $object, StrategyContainer $strategies): object|null
{
$docBlock = $this->createDocBlock($object->getDocComment(), $context->getTypeContext());
$enum = $context->peek();
assert($enum instanceof EnumElement);
$case = new EnumCaseElement(
$object->getAttribute('fqsen'),
$docBlock,
new Location($object->getLine()),
new Location($object->getEndLine()),
$this->determineValue($object),
);
$enum->addCase($case);
return $case;
}
private function determineValue(EnumCaseNode $value): ValueExpression|null
{
$expression = $value->expr !== null ? $this->prettyPrinter->prettyPrintExpr($value->expr) : null;
if ($expression === null) {
return null;
}
if ($this->prettyPrinter instanceof ExpressionPrinter) {
$expression = new ValueExpression($expression, $this->prettyPrinter->getParts());
}
if (is_string($expression)) {
$expression = new ValueExpression($expression, []);
}
return $expression;
}
}

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Override;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\File as FileElement;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Node\Stmt\Enum_ as EnumNode;
use function assert;
final class Enum_ extends AbstractFactory
{
#[Override]
public function matches(ContextStack $context, object $object): bool
{
return $object instanceof EnumNode;
}
/** @param EnumNode $object */
#[Override]
protected function doCreate(ContextStack $context, object $object, StrategyContainer $strategies): object|null
{
$docBlock = $this->createDocBlock($object->getDocComment(), $context->getTypeContext());
$enum = new \phpDocumentor\Reflection\Php\Enum_(
$object->getAttribute('fqsen'),
(new Type())->fromPhpParser($object->scalarType),
$docBlock,
new Location($object->getLine()),
new Location($object->getEndLine()),
);
foreach ($object->implements as $interfaceClassName) {
$enum->addInterface(
new Fqsen('\\' . $interfaceClassName->toString()),
);
}
$file = $context->peek();
assert($file instanceof FileElement);
$file->addEnum($enum);
foreach ($object->stmts as $stmt) {
$thisContext = $context->push($enum);
$strategy = $strategies->findMatching($thisContext, $stmt);
$strategy->create($thisContext, $stmt, $strategies);
}
return $enum;
}
}

View File

@@ -0,0 +1,197 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Override;
use phpDocumentor\Reflection\DocBlock as DocBlockInstance;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\File as FileSystemFile;
use phpDocumentor\Reflection\Middleware\ChainFactory;
use phpDocumentor\Reflection\Middleware\Middleware;
use phpDocumentor\Reflection\Php\Factory\File\CreateCommand;
use phpDocumentor\Reflection\Php\File as FileElement;
use phpDocumentor\Reflection\Php\NodesFactory;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\FileToContext;
use PhpParser\Comment\Doc;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_ as ClassNode;
use PhpParser\Node\Stmt\Const_ as ConstantNode;
use PhpParser\Node\Stmt\Declare_ as DeclareNode;
use PhpParser\Node\Stmt\Function_ as FunctionNode;
use PhpParser\Node\Stmt\InlineHTML;
use PhpParser\Node\Stmt\Interface_ as InterfaceNode;
use PhpParser\Node\Stmt\Trait_ as TraitNode;
use function array_merge;
use function in_array;
/**
* Strategy to create File element from the provided filename.
* This class supports extra middle wares to add extra steps to the creation process.
*/
final class File extends AbstractFactory
{
private const SKIPPED_NODE_TYPES = [
DeclareNode::class,
InlineHTML::class,
];
/** @var callable */
private $middlewareChain;
/**
* Initializes the object.
*
* @param Middleware[] $middleware
*/
public function __construct(
DocBlockFactoryInterface $docBlockFactory,
private readonly NodesFactory $nodesFactory,
array $middleware = [],
) {
parent::__construct($docBlockFactory);
$lastCallable = fn ($command): FileElement => $this->createFile($command);
$this->middlewareChain = ChainFactory::createExecutionChain($middleware, $lastCallable);
}
#[Override]
public function matches(ContextStack $context, object $object): bool
{
return $object instanceof FileSystemFile;
}
/**
* Creates an File out of the given object.
*
* Since an object might contain other objects that need to be converted the $factory is passed so it can be
* used to create nested Elements.
*
* @param ContextStack $context used to convert nested objects.
* @param FileSystemFile $object path to the file to convert to an File object.
* @param StrategyContainer $strategies used to convert nested objects.
*/
#[Override]
protected function doCreate(ContextStack $context, object $object, StrategyContainer $strategies): object|null
{
$command = new CreateCommand($context, $object, $strategies);
$middlewareChain = $this->middlewareChain;
$file = $middlewareChain($command);
if ($file === null) {
return null;
}
$context->getProject()->addFile($file);
return $file;
}
private function createFile(CreateCommand $command): FileElement
{
$file = $command->getFile();
$code = $file->getContents();
$nodes = $this->nodesFactory->create($code, $file->path());
$fileToContext = new FileToContext();
$typeContext = $fileToContext($nodes);
$docBlock = $this->createFileDocBlock($typeContext, $nodes);
$result = new FileElement(
$file->md5(),
$file->path(),
$code,
$docBlock,
);
$this->createElements($command->getContext()->push($result)->withTypeContext($typeContext), $nodes, $command->getStrategies());
return $result;
}
/** @param Node[] $nodes */
private function createElements(
ContextStack $contextStack,
array $nodes,
StrategyContainer $strategies,
): void {
foreach ($nodes as $node) {
$strategy = $strategies->findMatching($contextStack, $node);
$strategy->create($contextStack, $node, $strategies);
}
}
/** @param Node[] $nodes */
protected function createFileDocBlock(
Context|null $context = null,
array $nodes = [],
): DocBlockInstance|null {
$node = null;
$comments = [];
foreach ($nodes as $n) {
if (!in_array($n::class, self::SKIPPED_NODE_TYPES)) {
$node = $n;
break;
}
$comments = array_merge($comments, $n->getComments());
}
if (!$node instanceof Node) {
return null;
}
$comments = array_merge($comments, $node->getComments());
if (empty($comments)) {
return null;
}
$found = 0;
$firstDocBlock = null;
foreach ($comments as $comment) {
if (!$comment instanceof Doc) {
continue;
}
// If current node cannot have a docblock return the first comment as docblock for the file.
if (
!(
$node instanceof ConstantNode ||
$node instanceof ClassNode ||
$node instanceof FunctionNode ||
$node instanceof InterfaceNode ||
$node instanceof TraitNode
)
) {
return $this->createDocBlock($comment, $context);
}
++$found;
if ($firstDocBlock === null) {
$firstDocBlock = $comment;
} elseif ($found > 2) {
break;
}
}
if ($found === 2) {
return $this->createDocBlock($firstDocBlock, $context);
}
return null;
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory\File;
use phpDocumentor\Reflection\File;
use phpDocumentor\Reflection\Middleware\Command;
use phpDocumentor\Reflection\Php\Factory\ContextStack;
use phpDocumentor\Reflection\Php\StrategyContainer;
/**
* File Create command is used by the File Factory Strategy.
* The command is passed to the registered middle ware classes.
*/
final class CreateCommand implements Command
{
/**
* Initializes this command.
*/
public function __construct(private readonly ContextStack $context, private readonly File $file, private readonly StrategyContainer $strategies)
{
}
/**
* Returns the strategyContainer in this command context.
*/
public function getStrategies(): StrategyContainer
{
return $this->strategies;
}
public function getFile(): File
{
return $this->file;
}
public function getContext(): ContextStack
{
return $this->context;
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Override;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\File as FileElement;
use phpDocumentor\Reflection\Php\Function_ as FunctionDescriptor;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Node\Stmt\Function_ as FunctionNode;
use Webmozart\Assert\Assert;
/**
* Strategy to convert Function_ to FunctionDescriptor
*
* @see FunctionDescriptor
* @see \PhpParser\Node\
*/
final class Function_ extends AbstractFactory implements ProjectFactoryStrategy
{
#[Override]
public function matches(ContextStack $context, object $object): bool
{
return $object instanceof FunctionNode && $context->peek() instanceof FileElement;
}
/**
* Creates a FunctionDescriptor out of the given object including its child elements.
*
* @param ContextStack $context of the created object
* @param FunctionNode $object
*/
#[Override]
protected function doCreate(
ContextStack $context,
object $object,
StrategyContainer $strategies,
): object|null {
$file = $context->peek();
Assert::isInstanceOf($file, FileElement::class);
$function = new FunctionDescriptor(
$object->getAttribute('fqsen'),
$this->createDocBlock($object->getDocComment(), $context->getTypeContext()),
new Location($object->getLine()),
new Location($object->getEndLine()),
(new Type())->fromPhpParser($object->getReturnType()),
$object->byRef ?: false,
);
$file->addFunction($function);
$thisContext = $context->push($function);
foreach ($object->stmts as $stmt) {
$strategy = $strategies->findMatching($thisContext, $stmt);
$strategy->create($thisContext, $stmt, $strategies);
}
return $function;
}
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Override;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\Constant as ConstantElement;
use phpDocumentor\Reflection\Php\Expression;
use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter;
use phpDocumentor\Reflection\Php\File as FileElement;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Node\Stmt\Const_;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use Webmozart\Assert\Assert;
use function is_string;
/**
* Strategy to convert GlobalConstantIterator to ConstantElement
*
* @see ConstantElement
* @see GlobalConstantIterator
*/
final class GlobalConstant extends AbstractFactory
{
/**
* Initializes the object.
*/
public function __construct(DocBlockFactoryInterface $docBlockFactory, private readonly PrettyPrinter $valueConverter)
{
parent::__construct($docBlockFactory);
}
#[Override]
public function matches(ContextStack $context, object $object): bool
{
return $object instanceof Const_;
}
/**
* Creates an Constant out of the given object.
*
* Since an object might contain other objects that need to be converted the $factory is passed so it can be
* used to create nested Elements.
*
* @param ContextStack $context of the created object
* @param Const_ $object object to convert to an Element
* @param StrategyContainer $strategies used to convert nested objects.
*/
#[Override]
protected function doCreate(
ContextStack $context,
object $object,
StrategyContainer $strategies,
): object|null {
$constants = new GlobalConstantIterator($object);
$file = $context->peek();
Assert::isInstanceOf($file, FileElement::class);
foreach ($constants as $const) {
$file->addConstant(
new ConstantElement(
$const->getFqsen(),
$this->createDocBlock($const->getDocComment(), $context->getTypeContext()),
$this->determineValue($const),
new Location($const->getLine()),
new Location($const->getEndLine()),
),
);
}
return null;
}
private function determineValue(GlobalConstantIterator $value): Expression
{
$expression = $this->valueConverter->prettyPrintExpr($value->getValue());
if ($this->valueConverter instanceof ExpressionPrinter) {
$expression = new Expression($expression, $this->valueConverter->getParts());
}
if (is_string($expression)) {
$expression = new Expression($expression, []);
}
return $expression;
}
}

View File

@@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Iterator;
use Override;
use phpDocumentor\Reflection\Fqsen;
use PhpParser\Comment\Doc;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt\Const_;
/** @implements Iterator<int, GlobalConstantIterator> */
final class GlobalConstantIterator implements Iterator
{
/** @var int index of the current constant to use */
private int $index = 0;
/**
* Initializes the class with source data.
*/
public function __construct(private readonly Const_ $constant)
{
}
/**
* Gets line the node started in.
*
* @return int Line
*/
public function getLine(): int
{
return $this->constant->getLine();
}
/**
* Gets line the node ended in.
*
* @return int Line
*/
public function getEndLine(): int
{
return $this->constant->getEndLine();
}
/**
* Returns the name of the current constant.
*/
public function getName(): string
{
return (string) $this->constant->consts[$this->index]->name;
}
/**
* Returns the fqsen of the current constant.
*/
public function getFqsen(): Fqsen
{
return $this->constant->consts[$this->index]->getAttribute('fqsen');
}
/**
* Gets the doc comment of the node.
*
* The doc comment has to be the last comment associated with the node.
*/
public function getDocComment(): Doc|null
{
$docComment = $this->constant->consts[$this->index]->getDocComment();
if ($docComment === null) {
$docComment = $this->constant->getDocComment();
}
return $docComment;
}
public function getValue(): Expr
{
return $this->constant->consts[$this->index]->value;
}
/** @link http://php.net/manual/en/iterator.current.php */
#[Override]
public function current(): self
{
return $this;
}
/** @link http://php.net/manual/en/iterator.next.php */
#[Override]
public function next(): void
{
++$this->index;
}
/** @link http://php.net/manual/en/iterator.key.php */
#[Override]
public function key(): int|null
{
return $this->index;
}
/** @link http://php.net/manual/en/iterator.valid.php */
#[Override]
public function valid(): bool
{
return isset($this->constant->consts[$this->index]);
}
/** @link http://php.net/manual/en/iterator.rewind.php */
#[Override]
public function rewind(): void
{
$this->index = 0;
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory;
use Override;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Stmt\If_;
class IfStatement implements ProjectFactoryStrategy
{
#[Override]
public function matches(ContextStack $context, object $object): bool
{
return $object instanceof If_;
}
/** @param If_ $object */
#[Override]
public function create(ContextStack $context, object $object, StrategyContainer $strategies): void
{
foreach ($object->stmts as $stmt) {
$strategies->findMatching($context, $stmt)->create($context, $stmt, $strategies);
}
foreach ($object->elseifs as $elseIf) {
foreach ($elseIf->stmts as $stmt) {
$strategies->findMatching($context, $stmt)->create($context, $stmt, $strategies);
}
}
if (!($object->else instanceof Else_)) {
return;
}
foreach ($object->else->stmts as $stmt) {
$strategies->findMatching($context, $stmt)->create($context, $stmt, $strategies);
}
}
}

View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Override;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\File as FileElement;
use phpDocumentor\Reflection\Php\Interface_ as InterfaceElement;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Node\Stmt\Interface_ as InterfaceNode;
use Webmozart\Assert\Assert;
/**
* Strategy to create a InterfaceElement including all sub elements.
*/
final class Interface_ extends AbstractFactory implements ProjectFactoryStrategy
{
#[Override]
public function matches(ContextStack $context, object $object): bool
{
return $object instanceof InterfaceNode;
}
/**
* Creates an Interface_ out of the given object.
*
* Since an object might contain other objects that need to be converted the $factory is passed so it can be
* used to create nested Elements.
*
* @param ContextStack $context of the created object
* @param InterfaceNode $object object to convert to an Element
* @param StrategyContainer $strategies used to convert nested objects.
*/
#[Override]
protected function doCreate(
ContextStack $context,
object $object,
StrategyContainer $strategies,
): object|null {
$docBlock = $this->createDocBlock($object->getDocComment(), $context->getTypeContext());
$parents = [];
foreach ($object->extends as $extend) {
$parents['\\' . (string) $extend] = new Fqsen('\\' . (string) $extend);
}
$interface = new InterfaceElement(
$object->getAttribute('fqsen'),
$parents,
$docBlock,
new Location($object->getLine()),
new Location($object->getEndLine()),
);
$file = $context->peek();
Assert::isInstanceOf($file, FileElement::class);
$file->addInterface($interface);
foreach ($object->stmts as $stmt) {
$thisContext = $context->push($interface);
$strategy = $strategies->findMatching($thisContext, $stmt);
$strategy->create($thisContext, $stmt, $strategies);
}
return $interface;
}
}

View File

@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Override;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\Class_;
use phpDocumentor\Reflection\Php\Enum_;
use phpDocumentor\Reflection\Php\Interface_;
use phpDocumentor\Reflection\Php\Method as MethodDescriptor;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Php\Trait_;
use phpDocumentor\Reflection\Php\Visibility;
use PhpParser\Node\Stmt\ClassMethod;
use Webmozart\Assert\Assert;
use function is_array;
/**
* Strategy to create MethodDescriptor and arguments when applicable.
*/
final class Method extends AbstractFactory
{
#[Override]
public function matches(ContextStack $context, object $object): bool
{
return $object instanceof ClassMethod;
}
/**
* Creates an MethodDescriptor out of the given object including its child elements.
*
* @param ClassMethod $object object to convert to an MethodDescriptor
* @param ContextStack $context of the created object
*/
#[Override]
protected function doCreate(
ContextStack $context,
object $object,
StrategyContainer $strategies,
): object|null {
$methodContainer = $context->peek();
Assert::isInstanceOfAny(
$methodContainer,
[
Class_::class,
Interface_::class,
Trait_::class,
Enum_::class,
],
);
$method = new MethodDescriptor(
$object->getAttribute('fqsen'),
$this->buildVisibility($object),
$this->createDocBlock($object->getDocComment(), $context->getTypeContext()),
$object->isAbstract(),
$object->isStatic(),
$object->isFinal(),
new Location($object->getLine(), $object->getStartFilePos()),
new Location($object->getEndLine(), $object->getEndFilePos()),
(new Type())->fromPhpParser($object->getReturnType()),
$object->byRef ?: false,
);
$methodContainer->addMethod($method);
if (!is_array($object->stmts)) {
return $method;
}
$thisContext = $context->push($method);
foreach ($object->stmts as $stmt) {
$strategy = $strategies->findMatching($thisContext, $stmt);
$strategy->create($thisContext, $stmt, $strategies);
}
return $method;
}
/**
* Converts the visibility of the method to a valid Visibility object.
*/
private function buildVisibility(ClassMethod $node): Visibility
{
if ($node->isPrivate()) {
return new Visibility(Visibility::PRIVATE_);
}
if ($node->isProtected()) {
return new Visibility(Visibility::PROTECTED_);
}
return new Visibility(Visibility::PUBLIC_);
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory;
use InvalidArgumentException;
use Override;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\File as FileElement;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Types\NamespaceNodeToContext;
use PhpParser\Node\Stmt\Namespace_ as NamespaceNode;
use Webmozart\Assert\Assert;
use function get_debug_type;
use function sprintf;
class Namespace_ implements ProjectFactoryStrategy
{
#[Override]
public function matches(ContextStack $context, object $object): bool
{
return $object instanceof NamespaceNode;
}
/** @param NamespaceNode $object */
#[Override]
public function create(ContextStack $context, object $object, StrategyContainer $strategies): void
{
if (!$this->matches($context, $object)) {
throw new InvalidArgumentException(
sprintf(
'%s cannot handle objects with the type %s',
self::class,
get_debug_type($object),
),
);
}
$file = $context->peek();
Assert::isInstanceOf($file, FileElement::class);
$file->addNamespace($object->getAttribute('fqsen') ?? new Fqsen('\\'));
$typeContext = (new NamespaceNodeToContext())($object);
foreach ($object->stmts as $stmt) {
$strategy = $strategies->findMatching($context, $stmt);
$strategy->create($context->withTypeContext($typeContext), $stmt, $strategies);
}
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory;
use Override;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
class Noop implements ProjectFactoryStrategy
{
#[Override]
public function matches(ContextStack $context, object $object): bool
{
return true;
}
#[Override]
public function create(ContextStack $context, object $object, StrategyContainer $strategies): void
{
}
}

View File

@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Override;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\Class_;
use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer;
use phpDocumentor\Reflection\Php\Interface_;
use phpDocumentor\Reflection\Php\Property as PropertyDescriptor;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Php\Trait_;
use PhpParser\Node\Stmt\Property as PropertyNode;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use Webmozart\Assert\Assert;
/**
* Strategy to convert PropertyIterator to PropertyDescriptor
*
* @see PropertyDescriptor
* @see PropertyIterator
*/
final class Property extends AbstractFactory
{
/** @param iterable<Reducer> $reducers */
public function __construct(
DocBlockFactoryInterface $docBlockFactory,
private readonly PrettyPrinter $valueConverter,
iterable $reducers = [],
) {
parent::__construct($docBlockFactory, $reducers);
}
#[Override]
public function matches(ContextStack $context, object $object): bool
{
return $object instanceof PropertyNode;
}
/**
* Creates an PropertyDescriptor out of the given object.
*
* Since an object might contain other objects that need to be converted the $factory is passed so it can be
* used to create nested Elements.
*
* @param ContextStack $context used to convert nested objects.
* @param PropertyNode $object
*/
#[Override]
protected function doCreate(
ContextStack $context,
object $object,
StrategyContainer $strategies,
): object|null {
$propertyContainer = $context->peek();
Assert::isInstanceOfAny(
$propertyContainer,
[
Class_::class,
Trait_::class,
Interface_::class,
],
);
$iterator = new PropertyIterator($object);
foreach ($iterator as $stmt) {
$property = PropertyBuilder::create(
$this->valueConverter,
$this->docBlockFactory,
$strategies,
$this->reducers,
)
->fqsen($stmt->getFqsen())
->visibility($stmt)
->type($stmt->getType())
->docblock($stmt->getDocComment())
->default($stmt->getDefault())
->static($stmt->isStatic())
->startLocation(new Location($stmt->getLine()))
->endLocation(new Location($stmt->getEndLine()))
->readOnly($stmt->isReadonly())
->hooks($stmt->getHooks())
->build($context);
foreach ($this->reducers as $reducer) {
$property = $reducer->reduce($context, $object, $strategies, $property);
}
if ($property === null) {
continue;
}
$propertyContainer->addProperty($property);
}
return null;
}
}

View File

@@ -0,0 +1,372 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\NodeVisitor\FindingVisitor;
use phpDocumentor\Reflection\Php\AsymmetricVisibility;
use phpDocumentor\Reflection\Php\Expression;
use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter;
use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer;
use phpDocumentor\Reflection\Php\Property as PropertyElement;
use phpDocumentor\Reflection\Php\PropertyHook;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Php\Visibility;
use phpDocumentor\Reflection\Types\Context;
use PhpParser\Comment\Doc;
use PhpParser\Modifiers;
use PhpParser\Node;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Param;
use PhpParser\Node\PropertyHook as PropertyHookNode;
use PhpParser\NodeTraverser;
use PhpParser\PrettyPrinter;
use PhpParser\PrettyPrinterAbstract;
use function array_filter;
use function array_map;
use function count;
use function is_string;
use function method_exists;
/**
* This class is responsible for building a property element from a PhpParser node.
*
* @internal
*/
final class PropertyBuilder
{
private Fqsen $fqsen;
private Visibility $visibility;
private bool $readOnly = false;
private Identifier|Name|ComplexType|null $type;
private Doc|null $docblock = null;
private Expr|null $default = null;
private bool $static = false;
private Location $startLocation;
private Location $endLocation;
/** @var PropertyHookNode[] */
private array $hooks = [];
/** @param iterable<Reducer> $reducers */
private function __construct(
private PrettyPrinter|PrettyPrinterAbstract $valueConverter,
private DocBlockFactoryInterface $docBlockFactory,
private StrategyContainer $strategies,
private iterable $reducers,
) {
$this->visibility = new Visibility(Visibility::PUBLIC_);
}
/** @param iterable<Reducer> $reducers */
public static function create(
PrettyPrinter|PrettyPrinterAbstract $valueConverter,
DocBlockFactoryInterface $docBlockFactory,
StrategyContainer $strategies,
iterable $reducers,
): self {
return new self($valueConverter, $docBlockFactory, $strategies, $reducers);
}
public function fqsen(Fqsen $fqsen): self
{
$this->fqsen = $fqsen;
return $this;
}
public function visibility(Param|PropertyIterator $node): self
{
$this->visibility = $this->buildVisibility($node);
return $this;
}
public function type(Identifier|Name|ComplexType|null $type): self
{
$this->type = $type;
return $this;
}
public function readOnly(bool $readOnly): self
{
$this->readOnly = $readOnly;
return $this;
}
public function docblock(Doc|null $docblock): self
{
$this->docblock = $docblock;
return $this;
}
public function default(Expr|null $default): self
{
$this->default = $default;
return $this;
}
public function static(bool $static): self
{
$this->static = $static;
return $this;
}
public function startLocation(Location $startLocation): self
{
$this->startLocation = $startLocation;
return $this;
}
public function endLocation(Location $endLocation): self
{
$this->endLocation = $endLocation;
return $this;
}
/** @param PropertyHookNode[] $hooks */
public function hooks(array $hooks): self
{
$this->hooks = $hooks;
return $this;
}
public function build(ContextStack $context): PropertyElement
{
$hooks = array_filter(array_map(
fn (PropertyHookNode $hook) => $this->buildHook($hook, $context, $this->visibility),
$this->hooks,
));
// Check if this is a virtual property by examining all hooks
$isVirtual = $this->isVirtualProperty($this->hooks, $this->fqsen->getName());
return new PropertyElement(
$this->fqsen,
$this->visibility,
$this->docblock !== null ? $this->docBlockFactory->create($this->docblock->getText(), $context->getTypeContext()) : null,
$this->determineDefault($context->getTypeContext()),
$this->static,
$this->startLocation,
$this->endLocation,
(new Type())->fromPhpParser($this->type),
$this->readOnly,
$hooks,
$isVirtual,
);
}
/**
* Returns true when current property has asymmetric accessors.
*
* This method will always return false when your phpparser version is < 5.2
*/
private function isAsymmetric(Param|PropertyIterator $node): bool
{
if (method_exists($node, 'isPrivateSet') === false) {
return false;
}
return $node->isPublicSet() || $node->isProtectedSet() || $node->isPrivateSet();
}
private function buildVisibility(Param|PropertyIterator $node): Visibility
{
if ($this->isAsymmetric($node) === false) {
return $this->buildReadVisibility($node);
}
$readVisibility = $this->buildReadVisibility($node);
$writeVisibility = $this->buildWriteVisibility($node);
if ((string) $writeVisibility === (string) $readVisibility) {
return $readVisibility;
}
return new AsymmetricVisibility(
$readVisibility,
$writeVisibility,
);
}
private function buildReadVisibility(Param|PropertyIterator $node): Visibility
{
if ($node instanceof Param && method_exists($node, 'isPublic') === false) {
return $this->buildVisibilityFromFlags($node->flags);
}
if ($node->isPrivate()) {
return new Visibility(Visibility::PRIVATE_);
}
if ($node->isProtected()) {
return new Visibility(Visibility::PROTECTED_);
}
return new Visibility(Visibility::PUBLIC_);
}
private function buildVisibilityFromFlags(int $flags): Visibility
{
if ((bool) ($flags & Modifiers::PRIVATE) === true) {
return new Visibility(Visibility::PRIVATE_);
}
if ((bool) ($flags & Modifiers::PROTECTED) === true) {
return new Visibility(Visibility::PROTECTED_);
}
return new Visibility(Visibility::PUBLIC_);
}
private function buildWriteVisibility(Param|PropertyIterator $node): Visibility
{
if ($node->isPrivateSet()) {
return new Visibility(Visibility::PRIVATE_);
}
if ($node->isProtectedSet()) {
return new Visibility(Visibility::PROTECTED_);
}
return new Visibility(Visibility::PUBLIC_);
}
private function buildHook(PropertyHookNode $hook, ContextStack $context, Visibility $propertyVisibility): PropertyHook|null
{
$doc = $hook->getDocComment();
$result = new PropertyHook(
$hook->name->toString(),
$this->buildHookVisibility($hook->name->toString(), $propertyVisibility),
$doc !== null ? $this->docBlockFactory->create($doc->getText(), $context->getTypeContext()) : null,
$hook->isFinal(),
new Location($hook->getStartLine()),
new Location($hook->getEndLine()),
);
foreach ($this->reducers as $reducer) {
$result = $reducer->reduce($context, $hook, $this->strategies, $result);
}
if ($result === null) {
return $result;
}
$thisContext = $context->push($result);
foreach ($hook->getStmts() ?? [] as $stmt) {
$strategy = $this->strategies->findMatching($thisContext, $stmt);
$strategy->create($thisContext, $stmt, $this->strategies);
}
return $result;
}
/**
* Detects if a property is virtual by checking if any of its hooks reference the property itself.
*
* A virtual property is one where no defined hook references the property itself.
* For example, in the 'get' hook, it doesn't use $this->propertyName.
*
* @param PropertyHookNode[] $hooks The property hooks to check
* @param string $propertyName The name of the property
*
* @return bool True if the property is virtual, false otherwise
*/
private function isVirtualProperty(array $hooks, string $propertyName): bool
{
if (empty($hooks)) {
return false;
}
foreach ($hooks as $hook) {
$stmts = $hook->getStmts();
if ($stmts === null || count($stmts) === 0) {
continue;
}
$finder = new FindingVisitor(
static function (Node $node) use ($propertyName) {
// Check if the node is a property fetch that references the property
return $node instanceof PropertyFetch && $node->name instanceof Identifier &&
$node->name->toString() === $propertyName &&
$node->var instanceof Variable &&
$node->var->name === 'this';
},
);
$traverser = new NodeTraverser($finder);
$traverser->traverse($stmts);
if ($finder->getFoundNode() !== null) {
return false;
}
}
return true;
}
/**
* Builds the hook visibility based on the hook name and property visibility.
*
* @param string $hookName The name of the hook ('get' or 'set')
* @param Visibility $propertyVisibility The visibility of the property
*
* @return Visibility The appropriate visibility for the hook
*/
private function buildHookVisibility(string $hookName, Visibility $propertyVisibility): Visibility
{
if ($propertyVisibility instanceof AsymmetricVisibility === false) {
return $propertyVisibility;
}
return match ($hookName) {
'get' => $propertyVisibility->getReadVisibility(),
'set' => $propertyVisibility->getWriteVisibility(),
default => $propertyVisibility,
};
}
private function determineDefault(Context|null $context): Expression|null
{
if ($this->valueConverter instanceof ExpressionPrinter) {
$expression = $this->default !== null ? $this->valueConverter->prettyPrintExpr($this->default, $context) : null;
} else {
$expression = $this->default !== null ? $this->valueConverter->prettyPrintExpr($this->default) : null;
}
if ($expression === null) {
return null;
}
if ($this->valueConverter instanceof ExpressionPrinter) {
$expression = new Expression($expression, $this->valueConverter->getParts());
}
if (is_string($expression)) {
$expression = new Expression($expression, []);
}
return $expression;
}
}

View File

@@ -0,0 +1,251 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Iterator;
use Override;
use phpDocumentor\Reflection\Fqsen;
use PhpParser\Comment\Doc;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\PropertyHook;
use PhpParser\Node\Stmt\Property as PropertyNode;
use function method_exists;
use function property_exists;
/**
* This class acts like a combination of a PropertyNode and PropertyProperty to
* be able to create property descriptors using a normal strategy.
*
* @implements Iterator<int, PropertyIterator>
*/
final class PropertyIterator implements Iterator
{
/** @var int index of the current propertyProperty to use */
private int $index = 0;
/**
* Instantiates this iterator with the propertyNode to iterate.
*/
public function __construct(private readonly PropertyNode $property)
{
}
/**
* returns true when the current property is public.
*/
public function isPublic(): bool
{
return $this->property->isPublic();
}
/**
* Returns asymmetric accessor value for current property.
*
* This method will return the same value as {@see self::isPublic()} when your phpparser version is < 5.2
*/
public function isPublicSet(): bool
{
if ($this->isAsymmetric() === false) {
return $this->isPublic();
}
return $this->property->isPublic();
}
/**
* returns true when the current property is protected.
*/
public function isProtected(): bool
{
return $this->property->isProtected();
}
/**
* Returns asymetric accessor value for current property.
*
* This method will return the same value as {@see self::isProtected()} when your phpparser version is < 5.2
*/
public function isProtectedSet(): bool
{
if ($this->isAsymmetric() === false) {
return $this->isProtected();
}
return $this->property->isProtectedSet();
}
/**
* returns true when the current property is private.
*/
public function isPrivate(): bool
{
return $this->property->isPrivate();
}
/**
* Returns asymetric accessor value for current property.
*
* This method will return the same value as {@see self::isPrivate()} when your phpparser version is < 5.2
*/
public function isPrivateSet(): bool
{
if ($this->isAsymmetric() === false) {
return $this->isPrivate();
}
return $this->property->isPrivateSet();
}
/**
* Returns true when current property has asymetric accessors.
*
* This method will always return false when your phpparser version is < 5.2
*/
public function isAsymmetric(): bool
{
if (method_exists($this->property, 'isPrivateSet') === false) {
return false;
}
return $this->property->isPublicSet() || $this->property->isProtectedSet() || $this->property->isPrivateSet();
}
/**
* returns true when the current property is static.
*/
public function isStatic(): bool
{
return $this->property->isStatic();
}
/**
* returns true when the current property is readonly.
*/
public function isReadOnly(): bool
{
return $this->property->isReadOnly();
}
/**
* Gets line the node started in.
*/
public function getLine(): int
{
return $this->property->getLine();
}
/**
* Gets line the node started in.
*/
public function getEndLine(): int
{
return $this->property->getEndLine();
}
/**
* Gets the type of the property.
*/
public function getType(): Identifier|Name|ComplexType|null
{
return $this->property->type;
}
/**
* Gets the doc comment of the node.
*
* The doc comment has to be the last comment associated with the node.
*/
public function getDocComment(): Doc|null
{
$docComment = $this->property->props[$this->index]->getDocComment();
if ($docComment === null) {
$docComment = $this->property->getDocComment();
}
return $docComment;
}
/**
* returns the name of the current property.
*/
public function getName(): string
{
return (string) $this->property->props[$this->index]->name;
}
/**
* returns the default value of the current property.
*/
public function getDefault(): Expr|null
{
return $this->property->props[$this->index]->default;
}
/**
* Returns the fqsen of the current property.
*/
public function getFqsen(): Fqsen
{
return $this->property->props[$this->index]->getAttribute('fqsen');
}
/** @return PropertyHook[] */
public function getHooks(): array
{
if (property_exists($this->property, 'hooks') === false) {
return [];
}
return $this->property->hooks;
}
/** @link http://php.net/manual/en/iterator.current.php */
#[Override]
public function current(): self
{
return $this;
}
/** @link http://php.net/manual/en/iterator.next.php */
#[Override]
public function next(): void
{
++$this->index;
}
/** @link http://php.net/manual/en/iterator.key.php */
#[Override]
public function key(): int|null
{
return $this->index;
}
/** @link http://php.net/manual/en/iterator.valid.php */
#[Override]
public function valid(): bool
{
return isset($this->property->props[$this->index]);
}
/** @link http://php.net/manual/en/iterator.rewind.php */
#[Override]
public function rewind(): void
{
$this->index = 0;
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory\Reducer;
use InvalidArgumentException;
use Override;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\AttributeContainer;
use phpDocumentor\Reflection\Php\CallArgument;
use phpDocumentor\Reflection\Php\Factory\ContextStack;
use phpDocumentor\Reflection\Php\StrategyContainer;
use PhpParser\Node\Arg;
use PhpParser\Node\AttributeGroup;
use PhpParser\PrettyPrinter\Standard;
use function array_map;
use function assert;
use function property_exists;
use function sprintf;
final class Attribute implements Reducer
{
private readonly Standard $printer;
public function __construct()
{
$this->printer = new Standard();
}
#[Override]
public function reduce(
ContextStack $context,
object $object,
StrategyContainer $strategies,
object|null $carry,
): object|null {
if ($carry === null) {
return null;
}
if (property_exists($object, 'attrGroups') === false || isset($object->attrGroups) === false) {
return $carry;
}
if ($carry instanceof AttributeContainer === false) {
throw new InvalidArgumentException(sprintf('Attribute can not be added on %s', $carry::class));
}
foreach ($object->attrGroups as $attrGroup) {
assert($attrGroup instanceof AttributeGroup);
foreach ($attrGroup->attrs as $attr) {
$carry->addAttribute(
new \phpDocumentor\Reflection\Php\Attribute(
new Fqsen('\\' . $attr->name->toString()),
array_map($this->buildCallArgument(...), $attr->args),
),
);
}
}
return $carry;
}
private function buildCallArgument(Arg $arg): CallArgument
{
return new CallArgument(
$this->printer->prettyPrintExpr($arg->value),
$arg->name?->toString(),
);
}
}

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory\Reducer;
use Override;
use phpDocumentor\Reflection\Php\Argument as ArgumentDescriptor;
use phpDocumentor\Reflection\Php\Expression;
use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter;
use phpDocumentor\Reflection\Php\Factory\ContextStack;
use phpDocumentor\Reflection\Php\Factory\Type;
use phpDocumentor\Reflection\Php\Function_;
use phpDocumentor\Reflection\Php\Method;
use phpDocumentor\Reflection\Php\PropertyHook;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Types\Context;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Param;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use Webmozart\Assert\Assert;
use function is_string;
class Parameter implements Reducer
{
public function __construct(private readonly PrettyPrinter $valueConverter)
{
}
#[Override]
public function reduce(
ContextStack $context,
object $object,
StrategyContainer $strategies,
object|null $carry,
): object|null {
if ($object instanceof FunctionLike === false) {
return $carry;
}
if ($carry instanceof Method === false && $carry instanceof Function_ === false && $carry instanceof PropertyHook === false) {
return null;
}
foreach ($object->getParams() as $param) {
Assert::isInstanceOf($param->var, Variable::class);
$carry->addArgument(
new ArgumentDescriptor(
is_string($param->var->name) ? $param->var->name : $this->valueConverter->prettyPrintExpr($param->var->name),
(new Type())->fromPhpParser($param->type),
$this->determineDefault($param, $context->getTypeContext()),
$param->byRef,
$param->variadic,
),
);
}
return $carry;
}
private function determineDefault(Param $value, Context|null $context): Expression|null
{
if ($this->valueConverter instanceof ExpressionPrinter) {
$expression = $value->default !== null ? $this->valueConverter->prettyPrintExpr($value->default, $context) : null;
} else {
$expression = $value->default !== null ? $this->valueConverter->prettyPrintExpr($value->default) : null;
}
if ($expression === null) {
return null;
}
if ($this->valueConverter instanceof ExpressionPrinter) {
$expression = new Expression($expression, $this->valueConverter->getParts());
}
if (is_string($expression)) {
$expression = new Expression($expression, []);
}
return $expression;
}
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory\Reducer;
use phpDocumentor\Reflection\Php\Factory\ContextStack;
use phpDocumentor\Reflection\Php\StrategyContainer;
interface Reducer
{
/**
* @param TCarry|null $carry
*
* @return TCarry|null
*
* @template TCarry of object
*/
public function reduce(
ContextStack $context,
object $object,
StrategyContainer $strategies,
object|null $carry,
): object|null;
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\Factory;
use InvalidArgumentException;
use Override;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Class_;
use phpDocumentor\Reflection\Php\Enum_;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Php\Trait_;
use PhpParser\Node\Stmt\TraitUse as TraitUseNode;
final class TraitUse implements ProjectFactoryStrategy
{
#[Override]
public function matches(ContextStack $context, object $object): bool
{
return $object instanceof TraitUseNode;
}
/**
* @param ContextStack $context of the created object
* @param TraitUseNode $object
*/
#[Override]
public function create(ContextStack $context, object $object, StrategyContainer $strategies): void
{
if ($this->matches($context, $object) === false) {
throw new InvalidArgumentException('Does not match expected node');
}
$class = $context->peek();
if (
$class instanceof Class_ === false
&& $class instanceof Trait_ === false
&& $class instanceof Enum_ === false
) {
throw new InvalidArgumentException('Traits can only be used in classes, enums or other traits');
}
foreach ($object->traits as $trait) {
$class->addUsedTrait(new Fqsen($trait->toCodeString()));
}
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use Override;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\File as FileElement;
use phpDocumentor\Reflection\Php\StrategyContainer;
use phpDocumentor\Reflection\Php\Trait_ as TraitElement;
use PhpParser\Node\Stmt\Trait_ as TraitNode;
use Webmozart\Assert\Assert;
final class Trait_ extends AbstractFactory
{
#[Override]
public function matches(ContextStack $context, object $object): bool
{
return $object instanceof TraitNode;
}
/**
* Creates an TraitElement out of the given object.
*
* Since an object might contain other objects that need to be converted the $factory is passed so it can be
* used to create nested Elements.
*
* @param ContextStack $context used to convert nested objects.
* @param TraitNode $object
*/
#[Override]
protected function doCreate(ContextStack $context, object $object, StrategyContainer $strategies): object|null
{
$trait = new TraitElement(
$object->getAttribute('fqsen'),
$this->createDocBlock($object->getDocComment(), $context->getTypeContext()),
new Location($object->getLine()),
new Location($object->getEndLine()),
);
$file = $context->peek();
Assert::isInstanceOf($file, FileElement::class);
$file->addTrait($trait);
foreach ($object->stmts as $stmt) {
$thisContext = $context->push($trait);
$strategy = $strategies->findMatching($thisContext, $stmt);
$strategy->create($thisContext, $stmt, $strategies);
}
return $trait;
}
}

View File

@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php\Factory;
use InvalidArgumentException;
use phpDocumentor\Reflection\Type as TypeElement;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Identifier;
use PhpParser\Node\IntersectionType;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType;
use PhpParser\NodeAbstract;
use function array_map;
use function implode;
use function is_string;
use function sprintf;
final class Type
{
public function fromPhpParser(Identifier|Name|ComplexType|null $type, Context|null $context = null): TypeElement|null
{
if ($type === null) {
return null;
}
return (new TypeResolver())
->resolve($this->convertPhpParserTypeToString($type), $context);
}
private function convertPhpParserTypeToString(NodeAbstract|string $type): string
{
if (is_string($type)) {
return $type;
}
if ($type instanceof Identifier) {
return $type->toString();
}
if ($type instanceof Name) {
return $type->toString();
}
if ($type instanceof NullableType) {
return '?' . $this->convertPhpParserTypeToString($type->type);
}
if ($type instanceof UnionType) {
$typesAsStrings = array_map(
fn ($typeObject): string => $this->convertPhpParserTypeToString($typeObject),
$type->types,
);
return implode('|', $typesAsStrings);
}
if ($type instanceof IntersectionType) {
$typesAsStrings = array_map(
fn ($typeObject): string => $this->convertPhpParserTypeToString($typeObject),
$type->types,
);
return implode('&', $typesAsStrings);
}
throw new InvalidArgumentException(sprintf('Unsupported complex type %s', $type::class));
}
}

View File

@@ -0,0 +1,244 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use function basename;
/**
* Represents a file in the project.
*
* @api
*/
final class File implements MetaDataContainerInterface
{
use MetadataContainer;
private readonly string $name;
/** @var Fqsen[] */
private array $namespaces = [];
/** @var string[] */
private array $includes = [];
/** @var Function_[] */
private array $functions = [];
/** @var Constant[] */
private array $constants = [];
/** @var Class_[] */
private array $classes = [];
/** @var Interface_[] */
private array $interfaces = [];
/** @var Trait_[] */
private array $traits = [];
/** @var Enum_[] */
private array $enums = [];
/**
* Initializes a new file descriptor with the given hash of its contents.
*
* @param string $hash An MD5 hash of the contents if this file.
*/
public function __construct(private readonly string $hash, private readonly string $path, private readonly string $source = '', private readonly DocBlock|null $docBlock = null)
{
$this->name = basename($path);
}
/**
* Returns the hash of the contents for this file.
*/
public function getHash(): string
{
return $this->hash;
}
/**
* Retrieves the contents of this file.
*/
public function getSource(): string
{
return $this->source;
}
/**
* Returns the namespace fqsens that have been defined in this file.
*
* @return Fqsen[]
*/
public function getNamespaces(): array
{
return $this->namespaces;
}
/**
* Add namespace to file
*/
public function addNamespace(Fqsen $fqsen): void
{
$this->namespaces[(string) $fqsen] = $fqsen;
}
/**
* Returns a list of all includes that have been declared in this file.
*
* @return string[]
*/
public function getIncludes(): array
{
return $this->includes;
}
public function addInclude(string $include): void
{
$this->includes[$include] = $include;
}
/**
* Returns a list of constant descriptors contained in this file.
*
* @return Constant[]
*/
public function getConstants(): array
{
return $this->constants;
}
/**
* Add constant to this file.
*/
public function addConstant(Constant $constant): void
{
$this->constants[(string) $constant->getFqsen()] = $constant;
}
/**
* Returns a list of function descriptors contained in this file.
*
* @return Function_[]
*/
public function getFunctions(): array
{
return $this->functions;
}
/**
* Add function to this file.
*/
public function addFunction(Function_ $function): void
{
$this->functions[(string) $function->getFqsen()] = $function;
}
/**
* Returns a list of class descriptors contained in this file.
*
* @return Class_[]
*/
public function getClasses(): array
{
return $this->classes;
}
/**
* Add Class to this file.
*/
public function addClass(Class_ $class): void
{
$this->classes[(string) $class->getFqsen()] = $class;
}
/**
* Returns a list of interface descriptors contained in this file.
*
* @return Interface_[]
*/
public function getInterfaces(): array
{
return $this->interfaces;
}
/**
* Add interface to this file.
*/
public function addInterface(Interface_ $interface): void
{
$this->interfaces[(string) $interface->getFqsen()] = $interface;
}
/**
* Returns a list of trait descriptors contained in this file.
*
* @return Trait_[]
*/
public function getTraits(): array
{
return $this->traits;
}
/**
* Add trait to this file.
*/
public function addTrait(Trait_ $trait): void
{
$this->traits[(string) $trait->getFqsen()] = $trait;
}
public function addEnum(Enum_ $enum): void
{
$this->enums[(string) $enum->getFqsen()] = $enum;
}
/**
* Returns a list of enum descriptors contained in this file.
*
* @return Enum_[]
*/
public function getEnums(): array
{
return $this->enums;
}
/**
* Returns the file path relative to the project's root.
*/
public function getPath(): string
{
return $this->path;
}
/**
* Returns the DocBlock of the element if available
*/
public function getDocBlock(): DocBlock|null
{
return $this->docBlock;
}
/**
* Returns the full name of this file
*/
public function getName(): string
{
return $this->name;
}
}

View File

@@ -0,0 +1,138 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use Override;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Element;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Mixed_;
/**
* Descriptor representing a function
*
* @api
*/
// @codingStandardsIgnoreStart
final class Function_ implements Element, MetaDataContainerInterface, AttributeContainer
// // @codingStandardsIgnoreEnd
{
use MetadataContainer;
use HasAttributes;
/** @var Argument[] */
private array $arguments = [];
private readonly Location $location;
private readonly Location $endLocation;
private readonly Type $returnType;
/**
* Initializes the object.
*/
public function __construct(
/** @var Fqsen Full Qualified Structural Element Name */
private readonly Fqsen $fqsen,
private readonly DocBlock|null $docBlock = null,
Location|null $location = null,
Location|null $endLocation = null,
Type|null $returnType = null,
private readonly bool $hasReturnByReference = false,
) {
if ($location === null) {
$location = new Location(-1);
}
if ($endLocation === null) {
$endLocation = new Location(-1);
}
if ($returnType === null) {
$returnType = new Mixed_();
}
$this->location = $location;
$this->endLocation = $endLocation;
$this->returnType = $returnType;
}
/**
* Returns the arguments of this function.
*
* @return Argument[]
*/
public function getArguments(): array
{
return $this->arguments;
}
/**
* Add an argument to the function.
*/
public function addArgument(Argument $argument): void
{
$this->arguments[] = $argument;
}
/**
* Returns the Fqsen of the element.
*/
#[Override]
public function getFqsen(): Fqsen
{
return $this->fqsen;
}
/**
* Returns the name of the element.
*/
#[Override]
public function getName(): string
{
return $this->fqsen->getName();
}
/**
* Returns the DocBlock of the element if available
*/
public function getDocBlock(): DocBlock|null
{
return $this->docBlock;
}
public function getLocation(): Location
{
return $this->location;
}
public function getEndLocation(): Location
{
return $this->endLocation;
}
public function getReturnType(): Type
{
return $this->returnType;
}
public function getHasReturnByReference(): bool
{
return $this->hasReturnByReference;
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php;
trait HasAttributes
{
/** @var Attribute[] */
private array $attributes = [];
public function addAttribute(Attribute $attribute): void
{
$this->attributes[] = $attribute;
}
/** @return Attribute[] */
public function getAttributes(): array
{
return $this->attributes;
}
}

View File

@@ -0,0 +1,164 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use Override;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Element;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use Webmozart\Assert\Assert;
/**
* Descriptor representing an Interface.
*
* @api
*/
final class Interface_ implements Element, MetaDataContainerInterface, AttributeContainer
{
use MetadataContainer;
use HasAttributes;
/** @var Constant[] */
private array $constants = [];
/** @var Method[] */
private array $methods = [];
/** @var Property[] */
private array $properties = [];
private readonly Location $location;
private readonly Location $endLocation;
/**
* Initializes the object.
*
* @param Fqsen[] $parents
*/
public function __construct(
/** @var Fqsen Full Qualified Structural Element Name */
private readonly Fqsen $fqsen,
private array $parents = [],
private readonly DocBlock|null $docBlock = null,
Location|null $location = null,
Location|null $endLocation = null,
) {
Assert::allIsInstanceOf($parents, Fqsen::class);
$this->location = $location ?: new Location(-1);
$this->endLocation = $endLocation ?: new Location(-1);
}
/**
* Returns the constants of this interface.
*
* @return Constant[]
*/
public function getConstants(): array
{
return $this->constants;
}
/**
* Add constant to this interface.
*/
public function addConstant(Constant $constant): void
{
$this->constants[(string) $constant->getFqsen()] = $constant;
}
/**
* Returns the methods in this interface.
*
* @return Method[]
*/
public function getMethods(): array
{
return $this->methods;
}
/**
* Add method to this interface.
*/
public function addMethod(Method $method): void
{
$this->methods[(string) $method->getFqsen()] = $method;
}
/**
* Returns the properties of this interface.
*
* @return Property[]
*/
public function getProperties(): array
{
return $this->properties;
}
/**
* Add a property to this interface.
*/
public function addProperty(Property $property): void
{
$this->properties[(string) $property->getFqsen()] = $property;
}
/**
* Returns the Fqsen of the element.
*/
#[Override]
public function getFqsen(): Fqsen
{
return $this->fqsen;
}
/**
* Returns the name of the element.
*/
#[Override]
public function getName(): string
{
return $this->fqsen->getName();
}
/**
* Returns the DocBlock of this interface if available.
*/
public function getDocBlock(): DocBlock|null
{
return $this->docBlock;
}
/**
* Returns the Fqsen of the interfaces this interface is extending.
*
* @return Fqsen[]
*/
public function getParents(): array
{
return $this->parents;
}
public function getLocation(): Location
{
return $this->location;
}
public function getEndLocation(): Location
{
return $this->endLocation;
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\Exception;
use phpDocumentor\Reflection\Metadata\Metadata;
use function array_key_exists;
use function sprintf;
/** @internal This class is not part of the backwards compatibility promise */
trait MetadataContainer
{
/** @var Metadata[] */
private array $metadata = [];
/** @throws Exception When metadata key already exists. */
public function addMetadata(Metadata $metadata): void
{
if (array_key_exists($metadata->key(), $this->metadata)) {
throw new Exception(sprintf('Metadata with key "%s" already exists', $metadata->key()));
}
$this->metadata[$metadata->key()] = $metadata;
}
/** @return Metadata[] */
public function getMetadata(): array
{
return $this->metadata;
}
}

View File

@@ -0,0 +1,186 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use Override;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Element;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Mixed_;
/**
* Descriptor representing a Method in a Class, Interface or Trait.
*
* @api
*/
final class Method implements Element, MetaDataContainerInterface, AttributeContainer
{
use MetadataContainer;
use HasAttributes;
/** @var Argument[] */
private array $arguments = [];
private readonly Location $location;
private readonly Location $endLocation;
private readonly Type $returnType;
/**
* Initializes the all properties.
*
* @param Visibility|null $visibility when null is provided a default 'public' is set.
*/
public function __construct(
/** @var Fqsen Full Qualified Structural Element Name */
private readonly Fqsen $fqsen,
private Visibility|null $visibility = null,
/** @var DocBlock|null documentation of this method. */
private readonly DocBlock|null $docBlock = null,
private readonly bool $abstract = false,
private readonly bool $static = false,
private readonly bool $final = false,
Location|null $location = null,
Location|null $endLocation = null,
Type|null $returnType = null,
private readonly bool $hasReturnByReference = false,
) {
if ($this->visibility === null) {
$this->visibility = new Visibility('public');
}
if ($location === null) {
$location = new Location(-1);
}
if ($endLocation === null) {
$endLocation = new Location(-1);
}
if ($returnType === null) {
$returnType = new Mixed_();
}
$this->location = $location;
$this->endLocation = $endLocation;
$this->returnType = $returnType;
}
/**
* Returns true when this method is abstract. Otherwise returns false.
*/
public function isAbstract(): bool
{
return $this->abstract;
}
/**
* Returns true when this method is final. Otherwise returns false.
*/
public function isFinal(): bool
{
return $this->final;
}
/**
* Returns true when this method is static. Otherwise returns false.
*/
public function isStatic(): bool
{
return $this->static;
}
/**
* Returns the Visibility of this method.
*/
public function getVisibility(): Visibility|null
{
return $this->visibility;
}
/**
* Returns the arguments of this method.
*
* @return Argument[]
*/
public function getArguments(): array
{
return $this->arguments;
}
/**
* Add new argument to this method.
*/
public function addArgument(Argument $argument): void
{
$this->arguments[] = $argument;
}
/**
* Returns the Fqsen of the element.
*/
#[Override]
public function getFqsen(): Fqsen
{
return $this->fqsen;
}
/**
* Returns the name of the element.
*/
#[Override]
public function getName(): string
{
return $this->fqsen->getName();
}
/**
* Returns the DocBlock of this method if available.
*/
public function getDocBlock(): DocBlock|null
{
return $this->docBlock;
}
public function getLocation(): Location
{
return $this->location;
}
public function getEndLocation(): Location
{
return $this->endLocation;
}
/**
* Returns the in code defined return type.
*
* Return types are introduced in php 7.0 when your could doesn't have a
* return type defined this method will return Mixed_ by default. The return value of this
* method is not affected by the return tag in your docblock.
*/
public function getReturnType(): Type
{
return $this->returnType;
}
public function getHasReturnByReference(): bool
{
return $this->hasReturnByReference;
}
}

View File

@@ -0,0 +1,175 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\Element;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
/**
* Represents a namespace and its children for a project.
*
* @api
*/
// @codingStandardsIgnoreStart
final class Namespace_ implements Element, MetaDataContainerInterface
// codingStandardsIgnoreEnd
{
use MetadataContainer;
/**
* @var Fqsen[] fqsen of all functions in this namespace
*/
private array $functions = [];
/**
* @var Fqsen[] fqsen of all constants in this namespace
*/
private array $constants = [];
/**
* @var Fqsen[] fqsen of all classes in this namespace
*/
private array $classes = [];
/**
* @var Fqsen[] fqsen of all interfaces in this namespace
*/
private array $interfaces = [];
/**
* @var Fqsen[] fqsen of all traits in this namespace
*/
private array $traits = [];
/**
* Initializes the namespace.
*/
public function __construct(
/**
* @var Fqsen Full Qualified Structural Element Name
*/
private readonly Fqsen $fqsen
)
{
}
/**
* Returns a list of all fqsen of classes in this namespace.
*
* @return Fqsen[]
*/
public function getClasses(): array
{
return $this->classes;
}
/**
* Add a class to this namespace.
*/
public function addClass(Fqsen $class): void
{
$this->classes[(string) $class] = $class;
}
/**
* Returns a list of all constants in this namespace.
*
* @return Fqsen[]
*/
public function getConstants(): array
{
return $this->constants;
}
/**
* Add a Constant to this Namespace.
*/
public function addConstant(Fqsen $contant): void
{
$this->constants[(string) $contant] = $contant;
}
/**
* Returns a list of all functions in this namespace.
*
* @return Fqsen[]
*/
public function getFunctions(): array
{
return $this->functions;
}
/**
* Add a function to this namespace.
*/
public function addFunction(Fqsen $function): void
{
$this->functions[(string) $function] = $function;
}
/**
* Returns a list of all interfaces in this namespace.
*
* @return Fqsen[]
*/
public function getInterfaces(): array
{
return $this->interfaces;
}
/**
* Add an interface the this namespace.
*/
public function addInterface(Fqsen $interface): void
{
$this->interfaces[(string) $interface] = $interface;
}
/**
* Returns a list of all traits in this namespace.
*
* @return Fqsen[]
*/
public function getTraits(): array
{
return $this->traits;
}
/**
* Add a trait to this namespace.
*/
public function addTrait(Fqsen $trait): void
{
$this->traits[(string) $trait] = $trait;
}
/**
* Returns the Fqsen of the element.
*/
#[\Override]
public function getFqsen(): Fqsen
{
return $this->fqsen;
}
/**
* Returns the name of the element.
*/
#[\Override]
public function getName(): string
{
return $this->fqsen->getName();
}
}

View File

@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\Exception;
use phpDocumentor\Reflection\NodeVisitor\ElementNameResolver;
use PhpParser\Error;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeTraverserInterface;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\Parser;
use PhpParser\ParserFactory;
use Webmozart\Assert\Assert;
use function sprintf;
/**
* Factory to create a array of nodes from a provided file.
*
* This factory will use PhpParser and NodeTraverser to do the real processing.
*/
class NodesFactory
{
/**
* @param Parser $parser used to parse the code
* @param NodeTraverserInterface $traverser used to do some post processing on the nodes
*/
final public function __construct(private readonly Parser $parser, private readonly NodeTraverserInterface $traverser)
{
}
/**
* Creates a new instance of NodeFactory with default Parser ands Traverser.
*
* @param int $kind One of ParserFactory::PREFER_PHP7,
* ParserFactory::PREFER_PHP5, ParserFactory::ONLY_PHP7 or ParserFactory::ONLY_PHP5
*
* @return static
*/
public static function createInstance(int $kind = 1): self
{
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$traverser = new NodeTraverser();
$traverser->addVisitor(new NameResolver());
$traverser->addVisitor(new ElementNameResolver());
return new static($parser, $traverser);
}
/**
* Will convert the provided code to nodes.
*
* @param string $code code to process.
* @param string $filePath optional source file path for error context.
*
* @return Node[]
*
* @throws Exception when the provided code cannot be parsed.
*/
public function create(string $code, string $filePath = ''): array
{
try {
$nodes = $this->parser->parse($code);
} catch (Error $e) {
$line = $e->getStartLine();
$location = $filePath !== '' ? sprintf(' in %s', $filePath) : '';
$location .= $line > 0 ? sprintf(' on line %d', $line) : '';
throw new Exception(
sprintf('Syntax error%s: %s', $location, $e->getRawMessage()),
0,
$e,
);
}
Assert::isArray($nodes);
return $this->traverser->traverse($nodes);
}
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use Override;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Project as ProjectInterface;
/**
* Represents the entire project with its files, namespaces and indexes.
*
* @api
*/
final class Project implements ProjectInterface
{
/** @var File[] */
private array $files = [];
/** @var Namespace_[] */
private array $namespaces = [];
/**
* Initializes this descriptor.
*
* @param string $name Name of the current project.
* @param Namespace_|null $rootNamespace Root namespace of the project.
*/
public function __construct(private readonly string $name, private Namespace_|null $rootNamespace = null)
{
if ($this->rootNamespace !== null) {
return;
}
$this->rootNamespace = new Namespace_(new Fqsen('\\'));
}
/**
* Returns the name of this project.
*/
#[Override]
public function getName(): string
{
return $this->name;
}
/**
* Returns all files with their sub-elements.
*
* @return File[]
*/
public function getFiles(): array
{
return $this->files;
}
/**
* Add a file to this project.
*/
public function addFile(File $file): void
{
$this->files[$file->getPath()] = $file;
}
/**
* Returns all namespaces with their sub-elements.
*
* @return Namespace_[]
*/
public function getNamespaces(): array
{
return $this->namespaces;
}
/**
* Add a namespace to the project.
*/
public function addNamespace(Namespace_ $namespace): void
{
$this->namespaces[(string) $namespace->getFqsen()] = $namespace;
}
/**
* Returns the root (global) namespace.
*/
public function getRootNamespace(): Namespace_|null
{
return $this->rootNamespace;
}
}

View File

@@ -0,0 +1,215 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use Override;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\Exception;
use phpDocumentor\Reflection\File as SourceFile;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter;
use phpDocumentor\Reflection\Php\Factory\Class_;
use phpDocumentor\Reflection\Php\Factory\ClassConstant;
use phpDocumentor\Reflection\Php\Factory\ConstructorPromotion;
use phpDocumentor\Reflection\Php\Factory\ContextStack;
use phpDocumentor\Reflection\Php\Factory\Define;
use phpDocumentor\Reflection\Php\Factory\Enum_;
use phpDocumentor\Reflection\Php\Factory\EnumCase;
use phpDocumentor\Reflection\Php\Factory\Function_;
use phpDocumentor\Reflection\Php\Factory\GlobalConstant;
use phpDocumentor\Reflection\Php\Factory\IfStatement;
use phpDocumentor\Reflection\Php\Factory\Interface_;
use phpDocumentor\Reflection\Php\Factory\Method;
use phpDocumentor\Reflection\Php\Factory\Noop;
use phpDocumentor\Reflection\Php\Factory\Property;
use phpDocumentor\Reflection\Php\Factory\Reducer\Attribute;
use phpDocumentor\Reflection\Php\Factory\Reducer\Parameter;
use phpDocumentor\Reflection\Php\Factory\Trait_;
use phpDocumentor\Reflection\Php\Factory\TraitUse;
use phpDocumentor\Reflection\Project as ProjectInterface;
use phpDocumentor\Reflection\ProjectFactory as ProjectFactoryInterface;
use function is_array;
use const PHP_INT_MAX;
/**
* Factory class to transform files into a project description.
*/
final class ProjectFactory implements ProjectFactoryInterface
{
private readonly ProjectFactoryStrategies $strategies;
/**
* Initializes the factory with a number of strategies.
*
* @param ProjectFactoryStrategy[]|ProjectFactoryStrategies $strategies
*/
public function __construct(array|ProjectFactoryStrategies $strategies)
{
$this->strategies = is_array($strategies) ? new ProjectFactoryStrategies($strategies) : $strategies;
}
/**
* Creates a new instance of this factory. With all default strategies.
*/
public static function createInstance(): self
{
$docblockFactory = DocBlockFactory::createInstance();
$expressionPrinter = new ExpressionPrinter();
$attributeReducer = new Attribute();
$parameterReducer = new Parameter($expressionPrinter);
$methodStrategy = new Method($docblockFactory, [$attributeReducer, $parameterReducer]);
$strategies = new ProjectFactoryStrategies(
[
new \phpDocumentor\Reflection\Php\Factory\Namespace_(),
new Class_($docblockFactory, [$attributeReducer]),
new Enum_($docblockFactory, [$attributeReducer]),
new EnumCase($docblockFactory, $expressionPrinter, [$attributeReducer]),
new Define($docblockFactory, $expressionPrinter),
new GlobalConstant($docblockFactory, $expressionPrinter),
new ClassConstant($docblockFactory, $expressionPrinter, [$attributeReducer]),
new Factory\File($docblockFactory, NodesFactory::createInstance()),
new Function_($docblockFactory, [$attributeReducer, $parameterReducer]),
new Interface_($docblockFactory, [$attributeReducer]),
$methodStrategy,
new Property($docblockFactory, $expressionPrinter, [$attributeReducer, $parameterReducer]),
new Trait_($docblockFactory, [$attributeReducer]),
new IfStatement(),
new TraitUse(),
],
);
$strategies->addStrategy(
new ConstructorPromotion($methodStrategy, $docblockFactory, $expressionPrinter, [$attributeReducer, $parameterReducer]),
1100,
);
$strategies->addStrategy(new Noop(), -PHP_INT_MAX);
return new self($strategies);
}
public function addStrategy(
ProjectFactoryStrategy $strategy,
int $priority = ProjectFactoryStrategies::DEFAULT_PRIORITY,
): void {
$this->strategies->addStrategy($strategy, $priority);
}
/**
* Creates a project from the set of files.
*
* @param SourceFile[] $files
*
* @throws Exception When no matching strategy was found.
*/
#[Override]
public function create(string $name, array $files): ProjectInterface
{
$contextStack = new ContextStack(new Project($name), null);
foreach ($files as $filePath) {
$strategy = $this->strategies->findMatching($contextStack, $filePath);
$strategy->create($contextStack, $filePath, $this->strategies);
}
$project = $contextStack->getProject();
$this->buildNamespaces($project);
return $project;
}
/**
* Builds the namespace tree with all elements in the project.
*/
private function buildNamespaces(Project $project): void
{
foreach ($project->getFiles() as $file) {
foreach ($file->getNamespaces() as $namespaceFqsen) {
$namespace = $this->getNamespaceByName($project, (string) $namespaceFqsen);
$this->buildNamespace($file, $namespace);
}
}
}
/**
* Gets Namespace from the project if it exists, otherwise returns a new namepace
*/
private function getNamespaceByName(Project $project, string $name): Namespace_
{
$existingNamespaces = $project->getNamespaces();
if (isset($existingNamespaces[$name])) {
return $existingNamespaces[$name];
}
$namespace = new Namespace_(new Fqsen($name));
$project->addNamespace($namespace);
return $namespace;
}
/**
* Adds all elements belonging to the namespace to the namespace.
*/
private function buildNamespace(File $file, Namespace_ $namespace): void
{
foreach ($file->getClasses() as $class) {
if ($namespace->getFqsen() . '\\' . $class->getName() !== (string) $class->getFqsen()) {
continue;
}
$namespace->addClass($class->getFqsen());
}
foreach ($file->getInterfaces() as $interface) {
if ($namespace->getFqsen() . '\\' . $interface->getName() !== (string) $interface->getFqsen()) {
continue;
}
$namespace->addInterface($interface->getFqsen());
}
foreach ($file->getFunctions() as $function) {
if ($namespace->getFqsen() . '\\' . $function->getName() . '()' !== (string) $function->getFqsen()) {
continue;
}
$namespace->addFunction($function->getFqsen());
}
foreach ($file->getConstants() as $constant) {
if (
$namespace->getFqsen() . '::' . $constant->getName() !== (string) $constant->getFqsen() &&
$namespace->getFqsen() . '\\' . $constant->getName() !== (string) $constant->getFqsen()
) {
continue;
}
$namespace->addConstant($constant->getFqsen());
}
foreach ($file->getTraits() as $trait) {
if ($namespace->getFqsen() . '\\' . $trait->getName() !== (string) $trait->getFqsen()) {
continue;
}
$namespace->addTrait($trait->getFqsen());
}
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use OutOfBoundsException;
use Override;
use phpDocumentor\Reflection\Php\Factory\ContextStack;
use SplPriorityQueue;
use function get_debug_type;
use function sprintf;
final class ProjectFactoryStrategies implements StrategyContainer
{
public const DEFAULT_PRIORITY = 1000;
/** @var SplPriorityQueue<int, ProjectFactoryStrategy> */
private readonly SplPriorityQueue $strategies;
/**
* Initializes the factory with a number of strategies.
*
* @param ProjectFactoryStrategy[] $strategies
*/
public function __construct(array $strategies)
{
$this->strategies = new SplPriorityQueue();
foreach ($strategies as $strategy) {
$this->addStrategy($strategy);
}
}
/**
* Find the ProjectFactoryStrategy that matches $object.
*
* @throws OutOfBoundsException When no matching strategy was found.
*/
#[Override]
public function findMatching(ContextStack $context, mixed $object): ProjectFactoryStrategy
{
foreach (clone $this->strategies as $strategy) {
if ($strategy->matches($context, $object)) {
return $strategy;
}
}
throw new OutOfBoundsException(
sprintf(
'No matching factory found for %s',
get_debug_type($object),
),
);
}
/**
* Add a strategy to this container.
*/
public function addStrategy(ProjectFactoryStrategy $strategy, int $priority = self::DEFAULT_PRIORITY): void
{
$this->strategies->insert($strategy, $priority);
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\Php\Factory\ContextStack;
/**
* Interface for strategies used by the project factory to build Elements out of nodes.
*/
interface ProjectFactoryStrategy
{
/**
* Returns true when the strategy is able to handle the object.
*/
public function matches(ContextStack $context, object $object): bool;
/**
* Creates an Element out of the given object.
*
* Since an object might contain other objects that need to be converted the $stategies are passed so it can be
* used to create nested Elements. The passed ContextStack contains a stack of upstream created Elements that can
* be manipulated by factories. This allows the factory to also impact on parent objects of earlier
* created elements.
*
* @param Factory\ContextStack $context context to set the factory result.
* @param object $object object to convert to an Element
* @param StrategyContainer $strategies used to convert nested objects.
*/
public function create(ContextStack $context, object $object, StrategyContainer $strategies): void;
}

View File

@@ -0,0 +1,197 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use Override;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Element;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
use phpDocumentor\Reflection\Type;
use function is_string;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Descriptor representing a property.
*
* @api
*/
final class Property implements Element, MetaDataContainerInterface, AttributeContainer
{
use MetadataContainer;
use HasAttributes;
/** @var string[] $types */
private array $types = [];
private Visibility|null $visibility = null;
private readonly Location $location;
private readonly Location $endLocation;
/**
* @param Visibility|null $visibility when null is provided a default 'public' is set.
* @param PropertyHook[] $hooks
*/
public function __construct(
private readonly Fqsen $fqsen,
Visibility|null $visibility = null,
private readonly DocBlock|null $docBlock = null,
private Expression|string|null $default = null,
private readonly bool $static = false,
Location|null $location = null,
Location|null $endLocation = null,
private readonly Type|null $type = null,
private readonly bool $readOnly = false,
private readonly array $hooks = [],
private readonly bool $virtual = false,
) {
$this->visibility = $visibility ?: new Visibility('public');
$this->location = $location ?: new Location(-1);
$this->endLocation = $endLocation ?: new Location(-1);
if (!is_string($this->default)) {
return;
}
trigger_error(
'Default values for properties should be of type Expression, support for strings will be '
. 'removed in 7.x',
E_USER_DEPRECATED,
);
$this->default = new Expression($this->default, []);
}
/**
* Returns the default value for this property.
*/
public function getDefault(bool $asString = true): Expression|string|null
{
if ($this->default === null) {
return null;
}
if ($asString) {
trigger_error(
'The Default value will become of type Expression by default',
E_USER_DEPRECATED,
);
return (string) $this->default;
}
return $this->default;
}
/**
* Returns true when this method is static. Otherwise returns false.
*/
public function isStatic(): bool
{
return $this->static;
}
/**
* Returns the types of this property.
*
* @return string[]
*/
public function getTypes(): array
{
return $this->types;
}
/**
* Add a type to this property
*/
public function addType(string $type): void
{
$this->types[] = $type;
}
/**
* Return visibility of the property.
*/
public function getVisibility(): Visibility|null
{
return $this->visibility;
}
/**
* Returns the Fqsen of the element.
*/
#[Override]
public function getFqsen(): Fqsen
{
return $this->fqsen;
}
/**
* Returns the name of the element.
*/
#[Override]
public function getName(): string
{
return $this->fqsen->getName();
}
/**
* Returns the DocBlock of this property.
*/
public function getDocBlock(): DocBlock|null
{
return $this->docBlock;
}
public function getLocation(): Location
{
return $this->location;
}
public function getEndLocation(): Location
{
return $this->endLocation;
}
public function getType(): Type|null
{
return $this->type;
}
public function isReadOnly(): bool
{
return $this->readOnly;
}
/** @return PropertyHook[] */
public function getHooks(): array
{
return $this->hooks;
}
/**
* Returns true when this property is virtual (not explicitly backed).
*
* A virtual property is one where no defined hook references the property itself.
*/
public function isVirtual(): bool
{
return $this->virtual;
}
}

View File

@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
/** @api */
final class PropertyHook implements AttributeContainer, MetaDataContainerInterface
{
use MetadataContainer;
use HasAttributes;
/** @var Argument[] */
private array $arguments = [];
private readonly Location $location;
private readonly Location $endLocation;
public function __construct(
private readonly string $name,
private readonly Visibility $visibility,
private readonly DocBlock|null $docBlock = null,
private readonly bool $final = false,
Location|null $location = null,
Location|null $endLocation = null,
) {
$this->location = $location ?? new Location(-1);
$this->endLocation = $endLocation ?? new Location(-1);
}
/**
* Returns true when this hook is final. Otherwise, returns false.
*/
public function isFinal(): bool
{
return $this->final;
}
/**
* Returns the Visibility of this hook.
*/
public function getVisibility(): Visibility|null
{
return $this->visibility;
}
/**
* Returns the arguments of this hook.
*
* @return Argument[]
*/
public function getArguments(): array
{
return $this->arguments;
}
/**
* Add new argument to this hook.
*/
public function addArgument(Argument $argument): void
{
$this->arguments[] = $argument;
}
/**
* Returns the name of this hook.
*/
public function getName(): string
{
return $this->name;
}
/**
* Returns the DocBlock of this method if available.
*/
public function getDocBlock(): DocBlock|null
{
return $this->docBlock;
}
public function getLocation(): Location
{
return $this->location;
}
public function getEndLocation(): Location
{
return $this->endLocation;
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use phpDocumentor\Reflection\Exception;
use phpDocumentor\Reflection\Php\Factory\ContextStack;
/**
* Interface for strategy containers.
*/
interface StrategyContainer
{
/**
* Find the ProjectFactoryStrategy that matches $object.
*
* @throws Exception When no matching strategy was found.
*/
public function findMatching(ContextStack $context, mixed $object): ProjectFactoryStrategy;
}

View File

@@ -0,0 +1,175 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use Override;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Element;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
/**
* Descriptor representing a Trait.
*
* @api
*/
final class Trait_ implements Element, MetaDataContainerInterface, AttributeContainer
{
use MetadataContainer;
use HasAttributes;
/** @var Property[] $properties */
private array $properties = [];
/** @var Method[] $methods */
private array $methods = [];
/** @var Fqsen[] $usedTraits References to traits consumed by this trait */
private array $usedTraits = [];
private readonly Location $location;
private readonly Location $endLocation;
/** @var Constant[] */
private array $constants = [];
/**
* Initializes the all properties
*/
public function __construct(
/** @var Fqsen Full Qualified Structural Element Name */
private readonly Fqsen $fqsen,
private readonly DocBlock|null $docBlock = null,
Location|null $location = null,
Location|null $endLocation = null,
) {
if ($location === null) {
$location = new Location(-1);
}
if ($endLocation === null) {
$endLocation = new Location(-1);
}
$this->location = $location;
$this->endLocation = $endLocation;
}
/**
* Returns the methods of this Trait.
*
* @return Method[]
*/
public function getMethods(): array
{
return $this->methods;
}
/**
* Add a method to this Trait
*/
public function addMethod(Method $method): void
{
$this->methods[(string) $method->getFqsen()] = $method;
}
/**
* Returns the properties of this trait.
*
* @return Property[]
*/
public function getProperties(): array
{
return $this->properties;
}
/**
* Add a property to this Trait.
*/
public function addProperty(Property $property): void
{
$this->properties[(string) $property->getFqsen()] = $property;
}
/**
* Returns the Fqsen of the element.
*/
#[Override]
public function getFqsen(): Fqsen
{
return $this->fqsen;
}
/**
* Returns the name of the element.
*/
#[Override]
public function getName(): string
{
return $this->fqsen->getName();
}
public function getDocBlock(): DocBlock|null
{
return $this->docBlock;
}
/**
* Returns fqsen of all traits used by this trait.
*
* @return Fqsen[]
*/
public function getUsedTraits(): array
{
return $this->usedTraits;
}
/**
* Add reference to trait used by this trait.
*/
public function addUsedTrait(Fqsen $fqsen): void
{
$this->usedTraits[(string) $fqsen] = $fqsen;
}
public function getLocation(): Location
{
return $this->location;
}
public function getEndLocation(): Location
{
return $this->endLocation;
}
/**
* Returns the constants of this class.
*
* @return Constant[]
*/
public function getConstants(): array
{
return $this->constants;
}
/**
* Add Constant to this class.
*/
public function addConstant(Constant $constant): void
{
$this->constants[(string) $constant->getFqsen()] = $constant;
}
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Php\ValueEvaluator;
use phpDocumentor\Reflection\Php\Factory\ContextStack;
use PhpParser\ConstExprEvaluationException;
use PhpParser\ConstExprEvaluator;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar\MagicConst\Namespace_;
use function sprintf;
/** @internal */
final class ConstantEvaluator
{
/** @throws ConstExprEvaluationException */
public function evaluate(Expr $expr, ContextStack $contextStack): string
{
// @codeCoverageIgnoreStart
$evaluator = new ConstExprEvaluator(fn (Expr $expr): string => $this->evaluateFallback($expr, $contextStack));
return $evaluator->evaluateSilently($expr);
// @codeCoverageIgnoreEnd
}
/** @throws ConstExprEvaluationException */
private function evaluateFallback(Expr $expr, ContextStack $contextStack): string
{
$typeContext = $contextStack->getTypeContext();
if ($typeContext === null) {
throw new ConstExprEvaluationException(
sprintf('Expression of type %s cannot be evaluated', $expr->getType()),
);
}
if ($expr instanceof Namespace_) {
return $typeContext->getNamespace();
}
throw new ConstExprEvaluationException(
sprintf('Expression of type %s cannot be evaluated', $expr->getType()),
);
}
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Php;
use InvalidArgumentException;
use Override;
use Stringable;
use function sprintf;
use function strtolower;
/**
* Value object for visibility values of classes, properties, ect.
*
* @api
*/
class Visibility implements Stringable
{
/**
* constant for protected visibility
*/
public const PUBLIC_ = 'public';
/**
* constant for protected visibility
*/
public const PROTECTED_ = 'protected';
/**
* constant for private visibility
*/
public const PRIVATE_ = 'private';
/** @var string value can be public, protected or private */
private readonly string $visibility;
/**
* Initializes the object.
*
* @throws InvalidArgumentException When visibility does not match public|protected|private.
*/
public function __construct(string $visibility)
{
$visibility = strtolower($visibility);
if ($visibility !== self::PUBLIC_ && $visibility !== self::PROTECTED_ && $visibility !== self::PRIVATE_) {
throw new InvalidArgumentException(
sprintf('""%s" is not a valid visibility value.', $visibility),
);
}
$this->visibility = $visibility;
}
/**
* Will return a string representation of visibility.
*/
#[Override]
public function __toString(): string
{
return $this->visibility;
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Types;
use PhpParser\Node;
use PhpParser\Node\Stmt\GroupUse;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
use function array_filter;
use function array_map;
use function array_merge;
use function in_array;
/** @internal */
abstract class BaseToContext
{
/**
* @param GroupUse[]|Use_[] $usages
*
* @return array<string, string>
*/
protected static function flattenUsage(array $usages): array
{
return array_merge([], ...array_merge([], ...array_map(
static fn ($use): array => array_map(
static function (Node\UseItem|UseUse $useUse) use ($use): array {
if ($use instanceof GroupUse) {
return [
(string) $useUse->getAlias() => $use->prefix->toString() . '\\' . $useUse->name->toString(),
];
}
return [(string) $useUse->getAlias() => $useUse->name->toString()];
},
$use->uses,
),
$usages,
)));
}
/**
* @param Node[] $nodes
*
* @return Use_[]|GroupUse[]
*/
protected static function filterUsage(array $nodes): array
{
return array_filter(
$nodes,
static fn (Node $node): bool => (
$node instanceof Use_
|| $node instanceof GroupUse
) && in_array($node->type, [Use_::TYPE_UNKNOWN, Use_::TYPE_NORMAL], true),
);
}
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Types;
use PhpParser\Node;
final class FileToContext extends BaseToContext
{
/** @param Node[] $nodes */
public function __invoke(array $nodes): Context
{
return new Context(
'',
self::flattenUsage(self::filterUsage($nodes)),
);
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Types;
use PhpParser\Node\Stmt\Namespace_;
class NamespaceNodeToContext extends BaseToContext
{
public function __invoke(Namespace_|null $namespace): Context
{
if (!$namespace) {
return new Context('');
}
return new Context(
$namespace->name ? $namespace->name->toString() : '',
self::flattenUsage(self::filterUsage($namespace->stmts)),
);
}
}