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 @@
*.php text eol=lf

View File

@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 10
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -0,0 +1,24 @@
name: "Dependabot auto-merge"
on: # yamllint disable-line rule:truthy
pull_request_target: null
permissions:
contents: "write"
jobs:
dependabot:
runs-on: "ubuntu-latest"
if: "${{ github.actor == 'dependabot[bot]' }}"
steps:
- name: "Dependabot metadata"
id: "metadata"
uses: "dependabot/fetch-metadata@v2.5.0"
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: "Enable auto-merge for Dependabot PRs"
if: "${{ steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' }}"
run: "gh pr merge --auto --merge \"$PR_URL\""
env:
PR_URL: "${{github.event.pull_request.html_url}}"
GITHUB_TOKEN: "${{secrets.GITHUB_TOKEN}}"

View File

@@ -0,0 +1,61 @@
name: "Documentation"
on: # yamllint disable-line rule:truthy
push:
branches:
- "6.x"
pull_request: null
jobs:
documentation:
name: "Documentation"
runs-on: "ubuntu-latest"
steps:
- name: "Checkout"
uses: "actions/checkout@v6"
- name: "Build"
uses: "phpDocumentor/phpDocumentor@master"
- name: "Deploy"
if: "${{ github.event_name == 'push' && github.ref == 'refs/heads/6.x' }}"
uses: "actions/upload-artifact@v7"
with:
name: "documentation"
path: "build/docs"
retention-days: 1
deploy:
name: "Deploy"
if: "${{ github.event_name == 'push' && github.ref == 'refs/heads/6.x' }}"
runs-on: "ubuntu-latest"
needs: "documentation"
steps:
- name: "Checkout"
uses: "actions/checkout@v6"
with:
repository: "phpDocumentor/docs"
token: "${{ secrets.BOT_TOKEN }}"
path: "docs"
- name: "Download"
uses: "actions/download-artifact@v6"
with:
name: "documentation"
path: "build/docs"
- name: "Copy files"
run: "rsync -r --delete build/docs/* docs/docs/components/reflection"
- name: "Commit"
uses: "stefanzweifel/git-auto-commit-action@v7"
with:
repository: "docs"
commit_message: "Update reflection documentation"
- name: "Push"
uses: "ad-m/github-push-action@master"
with:
directory: "docs"
github_token: "${{ secrets.BOT_TOKEN }}"
repository: "phpDocumentor/docs"

View File

@@ -0,0 +1,71 @@
# https://docs.github.com/en/actions
name: "Integrate"
on: # yamllint disable-line rule:truthy
push:
branches:
- "6.x"
pull_request: null
# Allow manually triggering the workflow.
workflow_dispatch: null
jobs:
code-coverage:
name: "Code Coverage"
uses: "phpDocumentor/.github/.github/workflows/code-coverage.yml@v0.9"
with:
php-version: "8.2"
coding-standards:
name: "Coding Standards"
runs-on: "ubuntu-22.04"
steps:
- name: "Checkout"
uses: "actions/checkout@v6"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.2"
tools: "cs2pr"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
dependency-versions: "locked"
- name: "Run PHP_CodeSniffer"
run: "vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr"
dependency-analysis:
name: "Dependency analysis"
uses: "phpDocumentor/.github/.github/workflows/dependency-analysis.yml@v0.9"
with:
php-version: "8.2"
lint-root:
name: "Lint root"
uses: "phpDocumentor/.github/.github/workflows/lint.yml@v0.9"
with:
php-version: "8.2"
composer-options: "--no-check-publish --ansi"
static-analysis:
name: "Static analysis"
uses: "phpDocumentor/.github/.github/workflows/static-analysis.yml@main"
with:
php-version: "8.2"
php-extensions: "none, ctype, dom, json, mbstring, phar, simplexml, tokenizer, xml, xmlwriter, fileinfo, pcntl, posix"
unit-tests:
name: "Unit test"
uses: "phpDocumentor/.github/.github/workflows/continuous-integration.yml@v0.9"
integration-tests:
name: "Integration test"
uses: "phpDocumentor/.github/.github/workflows/continuous-integration.yml@v0.9"
needs: "unit-tests"
with:
test-suite: "integration"

View File

@@ -0,0 +1,21 @@
# IDE Shizzle; it is recommended to use a global .gitignore for this but since this is an OSS project we want to make
# it easy to contribute
.idea
/nbproject/private/
.buildpath
.project
.settings
# Build folder and vendor folder are generated code; no need to version this
build/
tools/
temp/
vendor/
*.phar
# By default the phpunit.xml.dist is provided; you can override this using a local config file
phpunit.xml
.phpunit.result.cache
.phpunit.cache
.phpdoc/cache

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<phive xmlns="https://phar.io/phive">
<phar name="phpunit" version="^9.5" installed="9.5.8" location="./tools/phpunit" copy="true"/>
<phar name="phpbench" version="^0.16.9" installed="0.16.9" location="./tools/phpbench" copy="true"/>
</phive>

View File

@@ -0,0 +1,15 @@
{% extends 'layout.html.twig' %}
{% set topMenu = {
"menu": [
{ "name": "About", "url": "https://phpdoc.org/"},
{ "name": "Components", "url": "https://phpdoc.org/components.html"},
{ "name": "Documentation", "url": "https://docs.phpdoc.org/"},
],
"social": [
{ "iconClass": "fab fa-mastodon", "url": "https://phpc.social/@phpdoc"},
{ "iconClass": "fab fa-github", "url": "https://github.com/phpdocumentor/typeresolver"},
{ "iconClass": "fas fa-envelope-open-text", "url": "https://github.com/orgs/phpDocumentor/discussions"}
]
}
%}

View File

@@ -0,0 +1,65 @@
extends: "default"
ignore: |
.build/
.notes/
vendor/
rules:
braces:
max-spaces-inside-empty: 0
max-spaces-inside: 1
min-spaces-inside-empty: 0
min-spaces-inside: 1
brackets:
max-spaces-inside-empty: 0
max-spaces-inside: 0
min-spaces-inside-empty: 0
min-spaces-inside: 0
colons:
max-spaces-after: 1
max-spaces-before: 0
commas:
max-spaces-after: 1
max-spaces-before: 0
min-spaces-after: 1
comments:
ignore-shebangs: true
min-spaces-from-content: 1
require-starting-space: true
comments-indentation: "enable"
document-end:
present: false
document-start:
present: false
indentation:
check-multi-line-strings: false
indent-sequences: true
spaces: 2
empty-lines:
max-end: 0
max-start: 0
max: 1
empty-values:
forbid-in-block-mappings: true
forbid-in-flow-mappings: true
hyphens:
max-spaces-after: 2
key-duplicates: "enable"
key-ordering: "disable"
line-length: "disable"
new-line-at-end-of-file: "enable"
new-lines:
type: "unix"
octal-values:
forbid-implicit-octal: true
quoted-strings:
quote-type: "double"
trailing-spaces: "enable"
truthy:
allowed-values:
- "false"
- "true"
yaml-files:
- "*.yaml"
- "*.yml"

21
vendor/phpdocumentor/reflection/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2010 Mike van Riel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,51 @@
.PHONY: help
help: ## Displays this list of targets with descriptions
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}'
.PHONY: code-style
code-style:
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:8.1-cli vendor/bin/phpcs
.PHONY: fix-code-style
fix-code-style:
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:8.1-cli vendor/bin/phpcbf
.PHONY: static-code-analysis
static-code-analysis: #vendor ## Runs a static code analysis with phpstan/phpstan and vimeo/psalm
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:8.2-cli vendor/bin/phpstan --configuration=phpstan.neon
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:8.2-cli vendor/bin/psalm.phar --show-info=true --threads=4
.PHONY: test
test: test-unit test-functional ## Runs all test suites with phpunit/phpunit
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:8.1-cli vendor/bin/phpunit
.PHONY: test-unit
test-unit: ## Runs unit tests with phpunit/phpunit
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:8.1-cli vendor/bin/phpunit --testsuite=unit
.PHONY: test-functional
test-functional: ## Runs unit tests with phpunit/phpunit
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:8.1-cli vendor/bin/phpunit --testsuite=integration
.PHONY: dependency-analysis
dependency-analysis: vendor ## Runs a dependency analysis with maglnet/composer-require-checker
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:8.1-cli .phive/composer-require-checker check --config-file=/opt/project/composer-require-checker.json
vendor: composer.json composer.lock
composer validate --no-check-publish
composer install --no-interaction --no-progress
.PHONY: benchmark
benchmark:
docker run -it --rm -v${CURDIR}:/opt/project -w /opt/project php:8.1-cli tools/phpbench run
.PHONY: rector
rector: ## Refactor code using rector
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:8.1-cli vendor/bin/rector process
.PHONY: pre-commit-test
pre-commit-test: fix-code-style test code-style static-code-analysis
.PHONY: docs
docs: ## Generate documentation
docker run -it --rm -v${PWD}:/opt/project -w /opt/project phpdoc/phpdoc:3-unstable

View File

@@ -0,0 +1,73 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
![Qa workflow](https://github.com/phpDocumentor/Reflection/workflows/Qa%20workflow/badge.svg)
[![Coveralls Coverage](https://img.shields.io/coveralls/github/phpDocumentor/Reflection.svg)](https://coveralls.io/github/phpDocumentor/Reflection?branch=master)
[![Scrutinizer Code Coverage](https://img.shields.io/scrutinizer/coverage/g/phpDocumentor/Reflection.svg)](https://scrutinizer-ci.com/g/phpDocumentor/Reflection/?branch=master)
[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/phpDocumentor/Reflection.svg)](https://scrutinizer-ci.com/g/phpDocumentor/Reflection/?branch=master)
[![Stable Version](https://img.shields.io/packagist/v/phpDocumentor/Reflection.svg)](https://packagist.org/packages/phpDocumentor/Reflection)
[![Unstable Version](https://img.shields.io/packagist/vpre/phpDocumentor/Reflection.svg)](https://packagist.org/packages/phpDocumentor/Reflection)
Reflection
==========
Using this library it is possible to statically reflect one or more files and create an object graph representing
your application's structure, including accompanying in-source documentation using DocBlocks.
The information that this library provides is similar to what the (built-in) Reflection extension of PHP provides; there
are however several advantages to using this library:
- Due to its Static nature it does not execute procedural code in your reflected files where Dynamic Reflection does.
- Because the none of the code is interpreted by PHP (and executed) Static Reflection uses less memory.
- Can reflect complete files
- Can reflect a whole project by reflecting multiple files.
- Reflects the contents of a DocBlock instead of just mentioning there is one.
- Is capable of analyzing code written for any PHP version (starting at 5.2) up to and including your installed
PHP version.
## Features
* [Creates an object graph] containing the structure of your application much like a site map shows the
structure of a website.
* Can read and interpret code of any PHP version starting with 5.2 up to and including your currently installed version
of PHP.
* Due it's clean interface it can be in any application without a complex setup.
## Installation
In order to inspect a codebase you need to tell composer to include the `phpdocumentor/reflection` package. This
can easily be done using the following command in your command line terminal:
composer require phpdocumentor/reflection:~6.0
After the installation is complete no further configuration is necessary and you can immediately start using it.
## Basic Usage
This Reflection library uses [PSR-4] and it is recommended to use a PSR-4 compatible autoloader to load all the
files containing the classes for this library.
An easy way to do this is by including the [composer] autoloader as shown here:
include 'vendor/autoload.php';
Once that is done you can use the `createInstance()` method of the `\phpDocumentor\Reflection\Php\ProjectFactory` class to instantiate a new project factory and
pre-configure it with sensible defaults. Optional you can specify the parser version that shall be used as an argument of `createInstance()`.
By default the php7 parser is prefered. And php5 is used as a fallback. See the [documentation of phpparser] for more info.
$projectFactory = \phpDocumentor\Reflection\Php\ProjectFactory::createInstance();
At this point we are ready to analyze your complete project or just one file at the time. Just pass an array of file paths to the `create` method of the project factory.
$projectFiles = [new \phpDocumentor\Reflection\File\LocalFile('tests/example.file.php')];
$project = $projectFactory->create('My Project', $projectFiles);
When the process is ready a new object of type `phpDocumentor\Reflection\Php\Project` will be returned that
contains a complete hierarchy of all files with their classes, traits and interfaces (and everything in there), but also
all namespaces and packages as a hierarchical tree.
> See the [example] script for a detailed and commented example
[PSR-4]: http://php-fig.com
[example]: example.php
[composer]: http://getcomposer.org
[documentation of phpparser]: https://github.com/nikic/PHP-Parser/blob/master/UPGRADE-2.0.md#creating-a-parser-instance

View File

@@ -0,0 +1,16 @@
{
"symbol-whitelist" : [
"null", "true", "false",
"static", "self", "parent",
"array", "string", "int", "float", "bool", "iterable", "callable", "void", "object",
"PhpParser\\Node\\Stmt\\PropertyProperty"
],
"php-core-extensions" : [
"Core",
"pcre",
"Reflection",
"tokenizer",
"SPL",
"standard"
]
}

View File

@@ -0,0 +1,70 @@
{
"name": "phpdocumentor/reflection",
"description": "Reflection library to do Static Analysis for PHP Projects",
"keywords": ["phpdoc", "phpDocumentor", "reflection", "static analysis"],
"homepage": "http://www.phpdoc.org",
"license": "MIT",
"autoload": {
"files": [
"src/php-parser/Modifiers.php"
],
"psr-4": {
"phpDocumentor\\": "src/phpDocumentor"
}
},
"autoload-dev": {
"psr-4": {
"phpDocumentor\\": [
"tests/unit/phpDocumentor",
"tests/bench/"
],
"phpDocumentor\\Reflection\\": [
"tests/integration"
]
}
},
"require": {
"php": "8.1.*|8.2.*|8.3.*|8.4.*|8.5.*",
"composer-runtime-api": "^2",
"nikic/php-parser": "~4.18 || ^5.0",
"phpdocumentor/reflection-common": "^2.1",
"phpdocumentor/reflection-docblock": "^5",
"phpdocumentor/type-resolver": "^1.4",
"symfony/polyfill-php80": "^1.28",
"webmozart/assert": "^1.7"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"doctrine/coding-standard": "^13.0",
"eliashaeussler/phpunit-attributes": "^1.8",
"mikey179/vfsstream": "~1.2",
"mockery/mockery": "~1.6.0",
"phpspec/prophecy-phpunit": "^2.4",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-webmozart-assert": "^1.2",
"phpunit/phpunit": "^10.5.53",
"psalm/phar": "^6.0",
"rector/rector": "^1.0.0",
"squizlabs/php_codesniffer": "^3.8"
},
"config": {
"preferred-install": {
"*": "dist"
},
"sort-packages": true,
"platform": {
"php": "8.1.0"
},
"allow-plugins": {
"phpstan/extension-installer": true,
"dealerdirect/phpcodesniffer-composer-installer": true
}
},
"extra": {
"branch-alias": {
"dev-5.x": "5.3.x-dev",
"dev-6.x": "6.0.x-dev"
}
}
}

3089
vendor/phpdocumentor/reflection/composer.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
Expressions
===========
Starting with version 5.4, we now support parsing expressions and extracting types and references to elements from them.
.. info::
An expression is, for example, the default value for a property or argument, the definition of an enum case or a
constant value. These are called expressions and can contain more complex combinations of operators and values.
As this library revolves around reflecting Static information, most parts of an expression are considered irrelevant;
except for type information -such as type hints- and references to other elements, such as constants. As such, whenever
an expression is interpreted, it will result in a string containing placeholders and an array containing the reflected
parts -such as FQSENs-.
This means that the getters like ``getDefault()`` will return a string or when you provide the optional argument
$isString as being false, it will return an Expression object; which, when cast to string, will provide the same result.
.. warning::
Deprecation: In version 6, we will remove the optional argument and always return an Expression object. When the
result was used as a string nothing will change, but code that checks if the output is a string will no longer
function starting from that version.
This will allow consumers to be able to extract types and links to elements from expressions. This allows consumers to,
for example, interpret the default value for a constructor promoted properties when it directly instantiates an object.
Creating expressions
--------------------
.. hint::
The description below is only for internal usage and to understand how expressions work, this library deals with
this by default.
In this library, we use the ExpressionPrinter to convert a PHP-Parser node -or expression- into an expression
like this::
$printer = new ExpressionPrinter();
$expressionTemplate = $printer->prettyPrintExpr($phpParserNode);
$expression = new Expression($expressionTemplate, $printer->getParts());
In the example above we assume that there is a PHP-Parser node representing an expression; this node is passed to the
ExpressionPrinter -which is an adapted PrettyPrinter from PHP-Parser- which will render the expression as a readable
template string containing placeholders, and a list of parts that can slot into the placeholders.
Consuming expressions
---------------------
When using this library, you can consume these expression objects either by
1. Directly casting them to a string - this will replace all placeholders with the stringified version of the parts
2. Use the render function - this will do the same as the previous methods but you can specify one or more overrides
for the placeholders in the expression
The second method can be used to create your own string values from the given parts and render, for example, links in
these locations.
Another way to use these expressions is to interpret the parts array, and through that way know which elements and
types are referred to in that expression.

View File

@@ -0,0 +1,34 @@
.. _extending:
Extend the library
==================
The model exposed by this library is closed for inheritance. We did this to ensure the model is stable and does not
change via external factors. The complexity of this project makes it hard to keep all the internal classes stable.
The model is designed to be cached and constructed very carefully to ensure performance and memory usage are optimal.
Metadata
--------
Metadata is a way to extend the model with additional information. We call this metadata, as all first class
elements in the reflected codebase are part of the model. Extra data can be added to these elements using metadata.
Elements supporting metadata are:
.. phpdoc:class-list:: [?(@.interfaces contains "\phpDocumentor\Reflection\Metadata\MetaDataContainer")]
.. phpdoc:name::
.. warning::
Adding metadata might break the posibilty to cache the model. Be carefull with circular references and large
objects. We do recommend to keep the metadata small and simple.
Continue reading :doc:`Creating your first metadata <meta-data>`_ to learn how to create your own metadata.
.. toctree::
:maxdepth: 1
:titlesonly:
:hidden:
meta-data

View File

@@ -0,0 +1,77 @@
Metadata
=====
The model of this library is as closed as possible.
Main reason is because consumers of the library should rely on cache.
A mutable and flexible interface of the model would most likely break the caching.
However after some time the users to this library started requesting for a more flexible format.
This is why metadata was introduced.
Create your first metadata
--------------------------
First step is to create your own metadata implementation.
.. code:: php
final class Hook implements \phpDocumentor\Reflection\Metadata\Metadata
{
private string $hook;
public function __construct(string $hook)
{
$this->hook = $hook;
}
public function key(): string
{
return "project-metadata";
}
public function hook(): string
{
return $this->hook;
}
}
.. note::
We do highly recommend to keep your metadata objects small.
When reflecting a large project the number of objects will grow fast.
Now we have an class that can be used it is time to create a :php:class:`\phpDocumentor\Reflection\Php\ProjectFactoryStrategy`.
Strategies are used to reflect nodes in the AST of `phpparser`_.
In the example below we are adding the Hook metadata to any functions containing a function call.
.. code:: php
use \phpDocumentor\Reflection\Php\Function;
final class HookStrategy implements \phpDocumentor\Reflection\Php\ProjectFactoryStrategy
{
public function matches(ContextStack $context, object $object): bool
{
return $this->context->peek() instanceof Function_ &&
$object instanceof \PhpParser\Node\Expr\FuncCall &&
((string)$object->name) === 'hook'
}
public function create(ContextStack $context, object $object, StrategyContainer $strategies): void
{
$method = $context->peek();
$method->addMetadata(new Hook($object->args[0]->value));
}
}
.. note::
To speed up the reflection of your project the default factory instance has a Noop strategy. This strategy will
ignore all statements that are not handled by any strategy. Keep this in mind when implementing your own strategies
especially the statements you are looking for are nested in other statements like a ``while`` loop.
Finally add your new strategy to the project factory.
.. code:: php
$factory = \phpDocumentor\Reflection\Php\ProjectFactory::createInstance();
$factory->addStrategy(new HookStrategy());
.. _phpparser: https://github.com/nikic/PHP-Parser/

View File

@@ -0,0 +1,49 @@
Getting started
===============
This page will give you a quick introduction to the `phpdocumentor/reflection` package and how to get started with it.
Installation
------------
In order to inspect a codebase you need to tell composer to include the `phpdocumentor/reflection` package. This
can easily be done using the following command in your command line terminal:
.. code-block:: bash
composer require phpdocumentor/reflection:~6.0
In order to use the library you need to include the autoloader of composer in your code.
This can be done by adding the following line to your code:
.. code-block:: php
<?php
require_once 'vendor/autoload.php';
After the installation is complete no further configuration is necessary and you can immediately start using it.
Basic usage
------------
The :php:class:`\phpDocumentor\Reflection\Php\ProjectFactory` class is the entry point to the library and is used to create a new
project object that contains all the information about your codebase. It is configured with sensible defaults. And for most
usecases you can just use it as is.
.. code-block:: php
$projectFactory = \phpDocumentor\Reflection\Php\ProjectFactory::createInstance();
At this point we are ready to analyze your complete project or just one file at the time. Just pass an array of file paths to the `create` method of the project factory.
.. code-block:: php
$projectFiles = [new \phpDocumentor\Reflection\File\LocalFile('tests/example.file.php')];
$project = $projectFactory->create('My Project', $projectFiles);
When the process is ready a new object of type :php:class:`phpDocumentor\Reflection\Php\Project` will be returned that
contains a complete hierarchy of all files with their classes, traits and interfaces (and everything in there), but also
all namespaces and packages as a hierarchical tree.
This library does not provide a way to access the structure of the codebase in a searchable way.
This is up to the consumer of the library to implement.

View File

@@ -0,0 +1,29 @@
Reflection
==========
Using this library it is possible to statically reflect one or more files and create an object graph representing
your application's structure, including accompanying in-source documentation using DocBlocks.
The information that this library provides is similar to what the (built-in) Reflection extension of PHP provides; there
are however several advantages to using this library:
- Due to its Static nature it does not execute procedural code in your reflected files where Dynamic Reflection does.
- Because the none of the code is interpreted by PHP (and executed) Static Reflection uses less memory.
- Can reflect complete files
- Can reflect a whole project by reflecting multiple files.
- Reflects the contents of a DocBlock instead of just mentioning there is one.
- Is capable of analyzing code written for any PHP version (starting at 5.2) up to the lastest version, even if your installed
PHP version is lower than the code you are reflecting.
.. note::
As this library focuses on reflecting the structure of the codebase, it does not provide any options to manipulate
the output. If you want to collect more information from the codebase you can read about :ref:`extending the library <extending>`.
.. toctree::
:hidden:
:maxdepth: 2
getting-started
reflection-structure
expressions
extending/index

View File

@@ -0,0 +1,20 @@
Reflection structure
====================
The project created by the :php:class:`\phpDocumentor\Reflection\Php\ProjectFactory` class contains a hierarchy of objects
that represent the structure of your codebase. This hierarchy includes:
Files
Each file is represented by an object of type :php:class:`\phpDocumentor\Reflection\Php\File` which contains
information about the file such as its name, path, and contents. But also the elements that are defined in the file.
Files can be accessed through the :php:method:`\phpDocumentor\Reflection\Php\Project::getFiles()` method of the project object.
Files are a flat list of all files that were analyzed, regardless of their location in the directory structure.
Namespaces
Namespaces are represented by objects of type :php:class:`\phpDocumentor\Reflection\Php\Namespace_`. Each namespace
contains a list of classes, interfaces, traits, and functions that are defined within it. Namespaces can be accessed
through the :php:method:`\phpDocumentor\Reflection\Php\Project::getNamespaces()` method of the project object.
Namespaces are hierarchical and can contain sub-namespaces.
Both namespaces and files do contain the other structural elements that are defined in them, such as classes, interfaces, traits, and functions.
This library does not provide a way to access the structure of the codebase in a searchable way. This is up to the consumer of the library to implement.

View File

@@ -0,0 +1,37 @@
<?php
/**
* Create a project map (similar to a sitemap) of project.
*
* The Reflection component is capable of analyzing one or more files into a hierarchy of objects representing the
* structure of your project. It does this by analyzing the source code of each individual file using the
* `analyze()` method in the Analyzer class.
*
* Because the Analyzer class requires a whole series of objects that interact together a factory method `create()`
* is available. This method instantiates all objects and provides a reasonable default to start using the Analyzer.
*
* There is also a Service Provider (`\phpDocumentor\Descriptor\ServiceProvider`) that can be used with either Silex
* or Cilex instead of using the factory method; this will make it easier to plug in your own features if you want to.
*/
// use Composer's autoloader to allow the application to automatically load all classes on request.
use phpDocumentor\Reflection\Php\Project;
include 'vendor/autoload.php';
// Create a new Analyzer with which we can analyze a PHP source file
$projectFactory = \phpDocumentor\Reflection\Php\ProjectFactory::createInstance();
// Create an array of files to analize.
$files = [ new \phpDocumentor\Reflection\File\LocalFile('tests/example.file.php') ];
//create a new project 'MyProject' containing all elements in the files.
/** @var Project $project */
$project = $projectFactory->create('MyProject', $files);
// As an example of what you can do, let's list all class names in the file 'tests/example.file.php'.
echo 'List all classes in the example source file: ' . PHP_EOL;
/** @var \phpDocumentor\Reflection\Php\Class_ $class */
foreach ($project->getFiles()['tests/example.file.php']->getClasses() as $class) {
echo '- ' . $class->getFqsen() . PHP_EOL;
}

View File

@@ -0,0 +1,7 @@
{
"bootstrap": "vendor/autoload.php",
"path": "tests/bench",
"extensions": [
"PhpBench\\Extensions\\XDebug\\XDebugExtension"
]
}

View File

@@ -0,0 +1,66 @@
<?xml version="1.0"?>
<ruleset name="Reflection">
<description>The coding standard for this library.</description>
<file>src</file>
<file>tests/unit</file>
<exclude-pattern>*/tests/unit/Types/ContextFactoryTest\.php</exclude-pattern>
<arg value="p"/>
<!-- Set the minimum PHP version for PHPCompatibility.
This should be kept in sync with the requirements in the composer.json file. -->
<config name="testVersion" value="8.1-"/>
<arg value="nps"/>
<rule ref="Doctrine">
<exclude name="Generic.Formatting.MultipleStatementAlignment.NotSame"/>
<exclude name="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousPrefix"/>
<exclude name="SlevomatCodingStandard.Classes.SuperfluousExceptionNaming.SuperfluousSuffix"/>
<exclude name="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming.SuperfluousSuffix"/>
<exclude name="SlevomatCodingStandard.Commenting.ForbiddenAnnotations"/>
<exclude name="Squiz.Commenting.FunctionComment"/>
</rule>
<rule ref="SlevomatCodingStandard.Commenting.ForbiddenAnnotations">
<properties>
<property name="forbiddenAnnotations" type="array">
<element value="@author"/>
<element value="@category"/>
<element value="@copyright"/>
<element value="@created"/>
<element value="@license"/>
<element value="@package"/>
<element value="@since"/>
<element value="@subpackage"/>
<element value="@version"/>
</property>
</properties>
</rule>
<rule ref="Generic.Formatting.SpaceAfterNot">
<properties>
<property name="spacing" value="0"/>
</properties>
</rule>
<rule ref="SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.NoAssignment">
<exclude-pattern>packages/guides/src/Setup/QuickStart.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.Numbers.RequireNumericLiteralSeparator">
<properties>
<property name="minDigitsBeforeDecimalPoint" value="5"/>
</properties>
</rule>
<rule ref="SlevomatCodingStandard.Functions.StaticClosure.ClosureNotStatic">
<exclude-pattern>*/tests/unit/*</exclude-pattern>
</rule>
<rule ref="Squiz.Classes.ValidClassName.NotCamelCaps">
<exclude-pattern>*/tests/unit/*</exclude-pattern>
<exclude-pattern>*/src/phpDocumentor/Reflection/Php/*</exclude-pattern>
</rule>
</ruleset>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" ?>
<phpdocumentor
configVersion="3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://www.phpdoc.org"
xsi:noNamespaceSchemaLocation="data/xsd/phpdoc.xsd"
>
<title>Guides</title>
<paths>
<output>build/docs</output>
</paths>
<version number="6.2.0">
<folder>latest</folder>
<api>
<source dsn=".">
<path>src/phpDocumentor</path>
</source>
<output>api</output>
<ignore hidden="true" symlinks="true">
<path>tests/**/*</path>
<path>build/**/*</path>
<path>var/**/*</path>
<path>vendor/**/*</path>
</ignore>
<extensions>
<extension>php</extension>
</extensions>
<ignore-tags>
<ignore-tag>template</ignore-tag>
<ignore-tag>template-extends</ignore-tag>
<ignore-tag>template-implements</ignore-tag>
<ignore-tag>extends</ignore-tag>
<ignore-tag>implements</ignore-tag>
</ignore-tags>
<default-package-name>phpDocumentor</default-package-name>
</api>
<guide>
<source dsn=".">
<path>docs</path>
</source>
<output>guides</output>
</guide>
</version>
<template name="default" />
</phpdocumentor>

View File

@@ -0,0 +1,21 @@
parameters:
paths:
- src
level: max
ignoreErrors:
- '#Method phpDocumentor\\Reflection\\File\\LocalFile::md5\(\) should return string but returns string\|false\.#'
#
# all these $fqsen errors indicate the need for a decorator class around PhpParser\Node to hold the public $fqsen that Reflection is giving it)
#
# src/phpDocumentor/Reflection/NodeVisitor/ElementNameResolver.php
- '#Method phpDocumentor\\Reflection\\Php\\Factory\\(.*)::getFqsen\(\) should return phpDocumentor\\Reflection\\Fqsen but returns mixed\.#'
- '#Parameter \#1 \$fqsen of class phpDocumentor\\Reflection\\Php\\(.*) constructor expects phpDocumentor\\Reflection\\Fqsen, mixed given\.#'
- '#Parameter \#1 \$fqsen of method phpDocumentor\\Reflection\\Php\\File::addNamespace\(\) expects phpDocumentor\\Reflection\\Fqsen, mixed given\.#'
#
# Type hint in php-parser is incorrect.
- '#Cannot cast PhpParser\\Node\\Expr\|string to string.#'
- '#Parameter \#2 \$object of method phpDocumentor\\Reflection\\Php\\ProjectFactoryStrategy::matches\(\) expects object, mixed given.#'
- '#Method phpDocumentor\\Reflection\\Php\\ValueEvaluator\\ConstantEvaluator::evaluate\(\) should return string but returns mixed.#'

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" colors="true" bootstrap="vendor/autoload.php" beStrictAboutOutputDuringTests="true" beStrictAboutChangesToGlobalState="true" cacheDirectory=".phpunit.cache" requireCoverageMetadata="true" beStrictAboutCoverageMetadata="false">
<extensions>
<bootstrap class="EliasHaeussler\PHPUnitAttributes\PHPUnitAttributesExtension" />
</extensions>
<coverage>
<report>
<clover outputFile="build/logs/clover.xml"/>
<html outputDirectory="build/coverage" lowUpperBound="35" highLowerBound="70"/>
</report>
</coverage>
<testsuites>
<testsuite name="unit">
<directory>./tests/unit</directory>
</testsuite>
<testsuite name="integration">
<directory>./tests/integration</directory>
</testsuite>
</testsuites>
<logging/>
<source>
<include>
<directory suffix=".php">./src/</directory>
</include>
</source>
</phpunit>

View File

@@ -0,0 +1,229 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="6.8.2@720ed6b578ac24f9543c65c3d4cecea0ff348ccd">
<file src="src/phpDocumentor/Reflection/File/LocalFile.php">
<FalsableReturnStatement>
<code><![CDATA[md5_file($this->path)]]></code>
</FalsableReturnStatement>
<InvalidFalsableReturnType>
<code><![CDATA[string]]></code>
</InvalidFalsableReturnType>
<UnusedClass>
<code><![CDATA[LocalFile]]></code>
</UnusedClass>
</file>
<file src="src/phpDocumentor/Reflection/Metadata/MetaDataContainer.php">
<PossiblyUnusedMethod>
<code><![CDATA[addMetadata]]></code>
<code><![CDATA[getMetadata]]></code>
</PossiblyUnusedMethod>
</file>
<file src="src/phpDocumentor/Reflection/Middleware/ChainFactory.php">
<MissingClosureParamType>
<code><![CDATA[$command]]></code>
</MissingClosureParamType>
<MixedArgument>
<code><![CDATA[$command]]></code>
</MixedArgument>
<MixedArgumentTypeCoercion>
<code><![CDATA[$lastCallable]]></code>
</MixedArgumentTypeCoercion>
</file>
<file src="src/phpDocumentor/Reflection/NodeVisitor/ElementNameResolver.php">
<DeprecatedClass>
<code><![CDATA[PropertyProperty::class]]></code>
<code><![CDATA[PropertyProperty::class]]></code>
</DeprecatedClass>
<ImplicitToStringCast>
<code><![CDATA[$node->name]]></code>
<code><![CDATA[$node->name]]></code>
<code><![CDATA[$node->name]]></code>
<code><![CDATA[$node->name]]></code>
<code><![CDATA[$node->name]]></code>
<code><![CDATA[$part]]></code>
</ImplicitToStringCast>
<MixedPropertyTypeCoercion>
<code><![CDATA[new SplDoublyLinkedList()]]></code>
</MixedPropertyTypeCoercion>
<PossiblyNullOperand>
<code><![CDATA[$part]]></code>
</PossiblyNullOperand>
</file>
<file src="src/phpDocumentor/Reflection/Php/AttributeContainer.php">
<PossiblyUnusedMethod>
<code><![CDATA[getAttributes]]></code>
</PossiblyUnusedMethod>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php">
<PossiblyUndefinedMethod>
<code><![CDATA[addConstant]]></code>
</PossiblyUndefinedMethod>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/ClassConstantIterator.php">
<MixedReturnStatement>
<code><![CDATA[$this->classConstants->consts[$this->index]->getAttribute('fqsen')]]></code>
</MixedReturnStatement>
<PossiblyUnusedMethod>
<code><![CDATA[getName]]></code>
<code><![CDATA[isPublic]]></code>
</PossiblyUnusedMethod>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/Class_.php">
<ImplicitToStringCast>
<code><![CDATA[$object->extends]]></code>
</ImplicitToStringCast>
<MixedArgument>
<code><![CDATA[$object->getAttribute('fqsen')]]></code>
</MixedArgument>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php">
<ImplicitToStringCast>
<code><![CDATA[$methodContainer->getFqsen()]]></code>
</ImplicitToStringCast>
<PossiblyInvalidCast>
<code><![CDATA[$param->var->name]]></code>
</PossiblyInvalidCast>
<RedundantCondition>
<code><![CDATA[$param->hooks]]></code>
</RedundantCondition>
<TypeDoesNotContainType>
<code><![CDATA[[]]]></code>
</TypeDoesNotContainType>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/EnumCase.php">
<MixedArgument>
<code><![CDATA[$object->getAttribute('fqsen')]]></code>
</MixedArgument>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/Enum_.php">
<MixedArgument>
<code><![CDATA[$object->getAttribute('fqsen')]]></code>
</MixedArgument>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/File.php">
<MissingClosureParamType>
<code><![CDATA[$command]]></code>
</MissingClosureParamType>
<MixedArgument>
<code><![CDATA[$command]]></code>
<code><![CDATA[$file]]></code>
</MixedArgument>
<MixedAssignment>
<code><![CDATA[$file]]></code>
</MixedAssignment>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/Function_.php">
<MixedArgument>
<code><![CDATA[$object->getAttribute('fqsen')]]></code>
</MixedArgument>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/GlobalConstantIterator.php">
<MixedReturnStatement>
<code><![CDATA[$this->constant->consts[$this->index]->getAttribute('fqsen')]]></code>
</MixedReturnStatement>
<PossiblyUnusedMethod>
<code><![CDATA[getName]]></code>
</PossiblyUnusedMethod>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/IfStatement.php">
<ClassMustBeFinal>
<code><![CDATA[IfStatement]]></code>
</ClassMustBeFinal>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/Interface_.php">
<MixedArgument>
<code><![CDATA[$object->getAttribute('fqsen')]]></code>
</MixedArgument>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/Method.php">
<MixedArgument>
<code><![CDATA[$object->getAttribute('fqsen')]]></code>
</MixedArgument>
<UndefinedMethod>
<code><![CDATA[addMethod]]></code>
</UndefinedMethod>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/Namespace_.php">
<ClassMustBeFinal>
<code><![CDATA[Namespace_]]></code>
</ClassMustBeFinal>
<MixedArgument>
<code><![CDATA[$object->getAttribute('fqsen') ?? new Fqsen('\\')]]></code>
</MixedArgument>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/Noop.php">
<ClassMustBeFinal>
<code><![CDATA[Noop]]></code>
</ClassMustBeFinal>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/Property.php">
<UndefinedMethod>
<code><![CDATA[addProperty]]></code>
</UndefinedMethod>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/PropertyBuilder.php">
<PropertyNotSetInConstructor>
<code><![CDATA[$endLocation]]></code>
<code><![CDATA[$fqsen]]></code>
<code><![CDATA[$startLocation]]></code>
<code><![CDATA[$type]]></code>
</PropertyNotSetInConstructor>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/PropertyIterator.php">
<MixedReturnStatement>
<code><![CDATA[$this->property->props[$this->index]->getAttribute('fqsen')]]></code>
</MixedReturnStatement>
<PossiblyUnusedMethod>
<code><![CDATA[getName]]></code>
</PossiblyUnusedMethod>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/Reducer/Parameter.php">
<ClassMustBeFinal>
<code><![CDATA[Parameter]]></code>
</ClassMustBeFinal>
</file>
<file src="src/phpDocumentor/Reflection/Php/Factory/Trait_.php">
<MixedArgument>
<code><![CDATA[$object->getAttribute('fqsen')]]></code>
</MixedArgument>
</file>
<file src="src/phpDocumentor/Reflection/Php/NodesFactory.php">
<ClassMustBeFinal>
<code><![CDATA[NodesFactory]]></code>
</ClassMustBeFinal>
<PossiblyUnusedParam>
<code><![CDATA[$kind]]></code>
</PossiblyUnusedParam>
</file>
<file src="src/phpDocumentor/Reflection/Php/ProjectFactory.php">
<ImplicitToStringCast>
<code><![CDATA[$namespace->getFqsen()]]></code>
<code><![CDATA[$namespace->getFqsen()]]></code>
<code><![CDATA[$namespace->getFqsen()]]></code>
<code><![CDATA[$namespace->getFqsen()]]></code>
<code><![CDATA[$namespace->getFqsen()]]></code>
<code><![CDATA[$namespace->getFqsen()]]></code>
</ImplicitToStringCast>
<PossiblyUnusedMethod>
<code><![CDATA[addStrategy]]></code>
<code><![CDATA[createInstance]]></code>
</PossiblyUnusedMethod>
</file>
<file src="src/phpDocumentor/Reflection/Php/ProjectFactoryStrategies.php">
<MixedArgument>
<code><![CDATA[$object]]></code>
</MixedArgument>
<MixedPropertyTypeCoercion>
<code><![CDATA[new SplPriorityQueue()]]></code>
</MixedPropertyTypeCoercion>
</file>
<file src="src/phpDocumentor/Reflection/Php/ValueEvaluator/ConstantEvaluator.php">
<MixedReturnStatement>
<code><![CDATA[$evaluator->evaluateSilently($expr)]]></code>
</MixedReturnStatement>
</file>
<file src="src/phpDocumentor/Reflection/Types/NamespaceNodeToContext.php">
<ClassMustBeFinal>
<code><![CDATA[NamespaceNodeToContext]]></code>
</ClassMustBeFinal>
</file>
</files>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<psalm
errorLevel="1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config file:///composer/vendor/vimeo/psalm/config.xsd"
errorBaseline="psalm-baseline.xml"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<!--
ExpressionPrinter inherits all kinds of errors from PHP-Parser that I cannot fix, until a solution is found
we ignore this
-->
<file name="src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php"/>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<LessSpecificReturnType errorLevel="info" />
<MoreSpecificImplementedParamType errorLevel="info" />
<DeprecatedConstant errorLevel="info" />
</issueHandlers>
</psalm>

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
__DIR__ . '/src',
__DIR__ . '/tests/unit'
]);
// register a single rule
$rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
$rectorConfig->rule(Rector\CodeQuality\Rector\Class_\CompleteDynamicPropertiesRector::class);
$rectorConfig->importNames();
// define sets of rules
$rectorConfig->sets([
LevelSetList::UP_TO_PHP_81,
]);
};

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)),
);
}
}

Some files were not shown because too many files have changed in this diff Show More