Работает обработка ссылок на таблицы с численностью

This commit is contained in:
2024-09-06 14:11:38 +03:00
parent 04374fef40
commit 2be45826c1
1698 changed files with 138656 additions and 174 deletions

View File

@ -0,0 +1,51 @@
# ChangeLog
All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
## [3.0.2] - 2024-07-03
### Changed
* This project now uses PHPStan instead of Psalm for static analysis
## [3.0.1] - 2024-03-02
### Changed
* Do not use implicitly nullable parameters
## [3.0.0] - 2024-02-02
### Removed
* This component is no longer supported on PHP 8.1
## [2.0.1] - 2024-03-02
### Changed
* Do not use implicitly nullable parameters
## [2.0.0] - 2023-02-03
### Removed
* This component is no longer supported on PHP 7.3, PHP 7.4, and PHP 8.0
## [1.0.1] - 2020-09-28
### Changed
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [1.0.0] - 2020-08-12
* Initial release
[3.0.2]: https://github.com/sebastianbergmann/cli-parser/compare/3.0.1...3.0.2
[3.0.1]: https://github.com/sebastianbergmann/cli-parser/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/sebastianbergmann/cli-parser/compare/2.0...3.0.0
[2.0.1]: https://github.com/sebastianbergmann/cli-parser/compare/2.0.0...2.0.1
[2.0.0]: https://github.com/sebastianbergmann/cli-parser/compare/1.0.1...2.0.0
[1.0.1]: https://github.com/sebastianbergmann/cli-parser/compare/1.0.0...1.0.1
[1.0.0]: https://github.com/sebastianbergmann/cli-parser/compare/bb7bb3297957927962b0a3335befe7b66f7462e9...1.0.0

29
vendor/sebastian/cli-parser/LICENSE vendored Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2020-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21
vendor/sebastian/cli-parser/README.md vendored Normal file
View File

@ -0,0 +1,21 @@
[![Latest Stable Version](https://poser.pugx.org/sebastian/cli-parser/v/stable.png)](https://packagist.org/packages/sebastian/cli-parser)
[![CI Status](https://github.com/sebastianbergmann/cli-parser/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/cli-parser/actions)
[![codecov](https://codecov.io/gh/sebastianbergmann/cli-parser/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/cli-parser)
# sebastian/cli-parser
Library for parsing `$_SERVER['argv']`, extracted from `phpunit/phpunit`.
## Installation
You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/):
```
composer require sebastian/cli-parser
```
If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:
```
composer require --dev sebastian/cli-parser
```

30
vendor/sebastian/cli-parser/SECURITY.md vendored Normal file
View File

@ -0,0 +1,30 @@
# Security Policy
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email `sebastian@phpunit.de`.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
## Web Context
The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit.
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.

View File

@ -0,0 +1,42 @@
{
"name": "sebastian/cli-parser",
"description": "Library for parsing CLI options",
"type": "library",
"homepage": "https://github.com/sebastianbergmann/cli-parser",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"support": {
"issues": "https://github.com/sebastianbergmann/cli-parser/issues",
"security": "https://github.com/sebastianbergmann/cli-parser/security/policy"
},
"prefer-stable": true,
"require": {
"php": ">=8.2"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
},
"config": {
"platform": {
"php": "8.2.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"autoload": {
"classmap": [
"src/"
]
},
"extra": {
"branch-alias": {
"dev-main": "3.0-dev"
}
}
}

View File

@ -0,0 +1,211 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/cli-parser.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CliParser;
use function array_map;
use function array_merge;
use function array_shift;
use function array_slice;
use function assert;
use function count;
use function current;
use function explode;
use function is_array;
use function is_int;
use function is_string;
use function key;
use function next;
use function preg_replace;
use function reset;
use function sort;
use function str_ends_with;
use function str_starts_with;
use function strlen;
use function strstr;
use function substr;
final class Parser
{
/**
* @param list<string> $argv
* @param list<string> $longOptions
*
* @throws AmbiguousOptionException
* @throws OptionDoesNotAllowArgumentException
* @throws RequiredOptionArgumentMissingException
* @throws UnknownOptionException
*
* @return array{0: list<array{0: non-empty-string, 1: ?non-empty-string}>, 1: list<non-empty-string>}
*/
public function parse(array $argv, string $shortOptions, ?array $longOptions = null): array
{
if (empty($argv)) {
return [[], []];
}
$options = [];
$nonOptions = [];
if ($longOptions !== null) {
sort($longOptions);
}
if (isset($argv[0][0]) && $argv[0][0] !== '-') {
array_shift($argv);
}
reset($argv);
$argv = array_map('trim', $argv);
while (false !== $arg = current($argv)) {
$i = key($argv);
assert(is_int($i));
next($argv);
if ($arg === '') {
continue;
}
if ($arg === '--') {
$nonOptions = array_merge($nonOptions, array_slice($argv, $i + 1));
break;
}
if ($arg[0] !== '-' || (strlen($arg) > 1 && $arg[1] === '-' && $longOptions === null)) {
$nonOptions[] = $arg;
continue;
}
if (strlen($arg) > 1 && $arg[1] === '-' && is_array($longOptions)) {
$this->parseLongOption(
substr($arg, 2),
$longOptions,
$options,
$argv,
);
continue;
}
$this->parseShortOption(
substr($arg, 1),
$shortOptions,
$options,
$argv,
);
}
return [$options, $nonOptions];
}
/**
* @param list<array{0: non-empty-string, 1: ?non-empty-string}> $options
* @param list<string> $argv
*
* @throws RequiredOptionArgumentMissingException
*/
private function parseShortOption(string $argument, string $shortOptions, array &$options, array &$argv): void
{
$argumentLength = strlen($argument);
for ($i = 0; $i < $argumentLength; $i++) {
$option = $argument[$i];
$optionArgument = null;
if ($argument[$i] === ':' || ($spec = strstr($shortOptions, $option)) === false) {
throw new UnknownOptionException('-' . $option);
}
if (strlen($spec) > 1 && $spec[1] === ':') {
if ($i + 1 < $argumentLength) {
$options[] = [$option, substr($argument, $i + 1)];
break;
}
if (!(strlen($spec) > 2 && $spec[2] === ':')) {
$optionArgument = current($argv);
if ($optionArgument === false) {
throw new RequiredOptionArgumentMissingException('-' . $option);
}
assert(is_string($optionArgument));
next($argv);
}
}
$options[] = [$option, $optionArgument];
}
}
/**
* @param list<string> $longOptions
* @param list<array{0: non-empty-string, 1: ?non-empty-string}> $options
* @param list<string> $argv
*
* @throws AmbiguousOptionException
* @throws OptionDoesNotAllowArgumentException
* @throws RequiredOptionArgumentMissingException
* @throws UnknownOptionException
*/
private function parseLongOption(string $argument, array $longOptions, array &$options, array &$argv): void
{
$count = count($longOptions);
$list = explode('=', $argument);
$option = $list[0];
$optionArgument = null;
if (count($list) > 1) {
$optionArgument = $list[1];
}
$optionLength = strlen($option);
foreach ($longOptions as $i => $longOption) {
$opt_start = substr($longOption, 0, $optionLength);
if ($opt_start !== $option) {
continue;
}
$opt_rest = substr($longOption, $optionLength);
if ($opt_rest !== '' && $i + 1 < $count && $option[0] !== '=' && str_starts_with($longOptions[$i + 1], $option)) {
throw new AmbiguousOptionException('--' . $option);
}
if (str_ends_with($longOption, '=')) {
if (!str_ends_with($longOption, '==') && !strlen((string) $optionArgument)) {
if (false === $optionArgument = current($argv)) {
throw new RequiredOptionArgumentMissingException('--' . $option);
}
next($argv);
}
} elseif ($optionArgument !== null) {
throw new OptionDoesNotAllowArgumentException('--' . $option);
}
$fullOption = '--' . preg_replace('/={1,2}$/', '', $longOption);
$options[] = [$fullOption, $optionArgument];
return;
}
throw new UnknownOptionException('--' . $option);
}
}

View File

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/cli-parser.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CliParser;
use function sprintf;
use RuntimeException;
final class AmbiguousOptionException extends RuntimeException implements Exception
{
public function __construct(string $option)
{
parent::__construct(
sprintf(
'Option "%s" is ambiguous',
$option,
),
);
}
}

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/cli-parser.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CliParser;
use Throwable;
interface Exception extends Throwable
{
}

View File

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/cli-parser.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CliParser;
use function sprintf;
use RuntimeException;
final class OptionDoesNotAllowArgumentException extends RuntimeException implements Exception
{
public function __construct(string $option)
{
parent::__construct(
sprintf(
'Option "%s" does not allow an argument',
$option,
),
);
}
}

View File

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/cli-parser.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CliParser;
use function sprintf;
use RuntimeException;
final class RequiredOptionArgumentMissingException extends RuntimeException implements Exception
{
public function __construct(string $option)
{
parent::__construct(
sprintf(
'Required argument for option "%s" is missing',
$option,
),
);
}
}

View File

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/cli-parser.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CliParser;
use function sprintf;
use RuntimeException;
final class UnknownOptionException extends RuntimeException implements Exception
{
public function __construct(string $option)
{
parent::__construct(
sprintf(
'Unknown option "%s"',
$option,
),
);
}
}

View File

@ -0,0 +1,59 @@
# Change Log
All notable changes to `sebastianbergmann/code-unit-reverse-lookup` are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [4.0.1] - 2024-07-03
### Changed
* This project now uses PHPStan instead of Psalm for static analysis
## [4.0.0] - 2024-02-02
### Removed
* This component is no longer supported on PHP 8.1
## [3.0.0] - 2023-02-03
### Removed
* This component is no longer supported on PHP 7.3, PHP 7.4, and PHP 8.0
## [2.0.3] - 2020-09-28
### Changed
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [2.0.2] - 2020-06-26
### Added
* This component is now supported on PHP 8
## [2.0.1] - 2020-06-15
### Changed
* Tests etc. are now ignored for archive exports
## 2.0.0 - 2020-02-07
### Removed
* This component is no longer supported on PHP 5.6, PHP 7.0, PHP 7.1, and PHP 7.2
## 1.0.0 - 2016-02-13
### Added
* Initial release
[4.0.1]: https://github.com/sebastianbergmann/code-unit-reverse-lookup/compare/4.0.0...4.0.1
[4.0.0]: https://github.com/sebastianbergmann/code-unit-reverse-lookup/compare/3.0...4.0.0
[3.0.0]: https://github.com/sebastianbergmann/code-unit-reverse-lookup/compare/2.0.3...3.0.0
[2.0.3]: https://github.com/sebastianbergmann/code-unit-reverse-lookup/compare/2.0.2...2.0.3
[2.0.2]: https://github.com/sebastianbergmann/code-unit-reverse-lookup/compare/2.0.1...2.0.2
[2.0.1]: https://github.com/sebastianbergmann/code-unit-reverse-lookup/compare/2.0.0...2.0.1
[2.0.0]: https://github.com/sebastianbergmann/code-unit-reverse-lookup/compare/1.0.0...2.0.0

View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2016-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,21 @@
[![Latest Stable Version](https://poser.pugx.org/sebastian/code-unit-reverse-lookup/v/stable.png)](https://packagist.org/packages/sebastian/code-unit-reverse-lookup)
[![CI Status](https://github.com/sebastianbergmann/code-unit-reverse-lookup/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/code-unit-reverse-lookup/actions)
[![codecov](https://codecov.io/gh/sebastianbergmann/code-unit-reverse-lookup/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/code-unit-reverse-lookup)
# sebastian/code-unit-reverse-lookup
Looks up which function or method a line of code belongs to.
## Installation
You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/):
```
composer require sebastian/code-unit-reverse-lookup
```
If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:
```
composer require --dev sebastian/code-unit-reverse-lookup
```

View File

@ -0,0 +1,30 @@
# Security Policy
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email `sebastian@phpunit.de`.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
## Web Context
The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit.
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.

View File

@ -0,0 +1,40 @@
{
"name": "sebastian/code-unit-reverse-lookup",
"description": "Looks up which function or method a line of code belongs to",
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"support": {
"issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
"security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy"
},
"config": {
"platform": {
"php": "8.2.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"prefer-stable": true,
"require": {
"php": ">=8.2"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
},
"autoload": {
"classmap": [
"src/"
]
},
"extra": {
"branch-alias": {
"dev-main": "4.0-dev"
}
}
}

View File

@ -0,0 +1,132 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit-reverse-lookup.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnitReverseLookup;
use function array_merge;
use function assert;
use function class_exists;
use function function_exists;
use function get_declared_classes;
use function get_declared_traits;
use function get_defined_functions;
use function is_array;
use function is_int;
use function is_string;
use function range;
use function trait_exists;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
final class Wizard
{
/**
* @var array<string, array<int, string>>
*/
private array $lookupTable = [];
/**
* @var array<class-string, true>
*/
private array $processedClasses = [];
/**
* @var array<string, true>
*/
private array $processedFunctions = [];
public function lookup(string $filename, int $lineNumber): string
{
if (!isset($this->lookupTable[$filename][$lineNumber])) {
$this->updateLookupTable();
}
if (isset($this->lookupTable[$filename][$lineNumber])) {
return $this->lookupTable[$filename][$lineNumber];
}
return $filename . ':' . $lineNumber;
}
private function updateLookupTable(): void
{
$this->processClassesAndTraits();
$this->processFunctions();
}
private function processClassesAndTraits(): void
{
$classes = get_declared_classes();
$traits = get_declared_traits();
assert(is_array($traits));
foreach (array_merge($classes, $traits) as $classOrTrait) {
assert(class_exists($classOrTrait) || trait_exists($classOrTrait));
if (isset($this->processedClasses[$classOrTrait])) {
continue;
}
foreach ((new ReflectionClass($classOrTrait))->getMethods() as $method) {
$this->processFunctionOrMethod($method);
}
$this->processedClasses[$classOrTrait] = true;
}
}
private function processFunctions(): void
{
foreach (get_defined_functions()['user'] as $function) {
assert(function_exists($function));
if (isset($this->processedFunctions[$function])) {
continue;
}
$this->processFunctionOrMethod(new ReflectionFunction($function));
$this->processedFunctions[$function] = true;
}
}
private function processFunctionOrMethod(ReflectionFunction|ReflectionMethod $functionOrMethod): void
{
if ($functionOrMethod->isInternal()) {
return;
}
$name = $functionOrMethod->getName();
if ($functionOrMethod instanceof ReflectionMethod) {
$name = $functionOrMethod->getDeclaringClass()->getName() . '::' . $name;
}
$fileName = $functionOrMethod->getFileName();
assert(is_string($fileName));
if (!isset($this->lookupTable[$fileName])) {
$this->lookupTable[$fileName] = [];
}
$startLine = $functionOrMethod->getStartLine();
$endLine = $functionOrMethod->getEndLine();
assert(is_int($startLine));
assert(is_int($endLine));
assert($endLine >= $startLine);
foreach (range($startLine, $endLine) as $line) {
$this->lookupTable[$fileName][$line] = $name;
}
}
}

92
vendor/sebastian/code-unit/ChangeLog.md vendored Normal file
View File

@ -0,0 +1,92 @@
# ChangeLog
All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [3.0.1] - 2024-07-03
### Changed
* This project now uses PHPStan instead of Psalm for static analysis
## [3.0.0] - 2024-02-02
### Removed
* This component is no longer supported on PHP 8.1
## [2.0.0] - 2023-02-03
### Added
* Added `SebastianBergmann\CodeUnit\FileUnit` value object that represents a sourcecode file
### Removed
* `SebastianBergmann\CodeUnit\CodeUnitCollection::fromArray()` has been removed
* `SebastianBergmann\CodeUnit\Mapper::stringToCodeUnits()` no longer supports `ClassName<*>`
* This component is no longer supported on PHP 7.3, PHP 7.4, and PHP 8.0
## [1.0.8] - 2020-10-26
### Fixed
* `SebastianBergmann\CodeUnit\Exception` now correctly extends `\Throwable`
## [1.0.7] - 2020-10-02
### Fixed
* `SebastianBergmann\CodeUnit\Mapper::stringToCodeUnits()` no longer attempts to create `CodeUnit` objects for code units that are not declared in userland
## [1.0.6] - 2020-09-28
### Changed
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [1.0.5] - 2020-06-26
### Fixed
* [#3](https://github.com/sebastianbergmann/code-unit/issues/3): Regression in 1.0.4
## [1.0.4] - 2020-06-26
### Added
* This component is now supported on PHP 8
## [1.0.3] - 2020-06-15
### Changed
* Tests etc. are now ignored for archive exports
## [1.0.2] - 2020-04-30
### Fixed
* `Mapper::stringToCodeUnits()` raised the wrong exception for `Class::method` when a class named `Class` exists but does not have a method named `method`
## [1.0.1] - 2020-04-27
### Fixed
* [#2](https://github.com/sebastianbergmann/code-unit/issues/2): `Mapper::stringToCodeUnits()` breaks when `ClassName<extended>` is used for class that extends built-in class
## [1.0.0] - 2020-03-30
* Initial release
[3.0.1]: https://github.com/sebastianbergmann/code-unit/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/sebastianbergmann/code-unit/compare/2.0...3.0.0
[2.0.0]: https://github.com/sebastianbergmann/code-unit/compare/1.0.8...2.0.0
[1.0.8]: https://github.com/sebastianbergmann/code-unit/compare/1.0.7...1.0.8
[1.0.7]: https://github.com/sebastianbergmann/code-unit/compare/1.0.6...1.0.7
[1.0.6]: https://github.com/sebastianbergmann/code-unit/compare/1.0.5...1.0.6
[1.0.5]: https://github.com/sebastianbergmann/code-unit/compare/1.0.4...1.0.5
[1.0.4]: https://github.com/sebastianbergmann/code-unit/compare/1.0.3...1.0.4
[1.0.3]: https://github.com/sebastianbergmann/code-unit/compare/1.0.2...1.0.3
[1.0.2]: https://github.com/sebastianbergmann/code-unit/compare/1.0.1...1.0.2
[1.0.1]: https://github.com/sebastianbergmann/code-unit/compare/1.0.0...1.0.1
[1.0.0]: https://github.com/sebastianbergmann/code-unit/compare/530c3900e5db9bcb8516da545bef0d62536cedaa...1.0.0

29
vendor/sebastian/code-unit/LICENSE vendored Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2020-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21
vendor/sebastian/code-unit/README.md vendored Normal file
View File

@ -0,0 +1,21 @@
[![Latest Stable Version](https://poser.pugx.org/sebastian/code-unit/v/stable.png)](https://packagist.org/packages/sebastian/code-unit)
[![CI Status](https://github.com/sebastianbergmann/code-unit/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/code-unit/actions)
[![codecov](https://codecov.io/gh/sebastianbergmann/code-unit/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/code-unit)
# sebastian/code-unit
Collection of value objects that represent the PHP code units.
## Installation
You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/):
```
composer require sebastian/code-unit
```
If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:
```
composer require --dev sebastian/code-unit
```

30
vendor/sebastian/code-unit/SECURITY.md vendored Normal file
View File

@ -0,0 +1,30 @@
# Security Policy
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email `sebastian@phpunit.de`.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
## Web Context
The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit.
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.

View File

@ -0,0 +1,51 @@
{
"name": "sebastian/code-unit",
"description": "Collection of value objects that represent the PHP code units",
"type": "library",
"homepage": "https://github.com/sebastianbergmann/code-unit",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"support": {
"issues": "https://github.com/sebastianbergmann/code-unit/issues",
"security": "https://github.com/sebastianbergmann/code-unit/security/policy"
},
"prefer-stable": true,
"require": {
"php": ">=8.2"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
},
"config": {
"platform": {
"php": "8.2.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"autoload": {
"classmap": [
"src/"
]
},
"autoload-dev": {
"classmap": [
"tests/_fixture"
],
"files": [
"tests/_fixture/file_with_multiple_code_units.php",
"tests/_fixture/function.php"
]
},
"extra": {
"branch-alias": {
"dev-main": "3.0-dev"
}
}
}

View File

@ -0,0 +1,21 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
/**
* @immutable
*/
final readonly class ClassMethodUnit extends CodeUnit
{
public function isClassMethod(): bool
{
return true;
}
}

View File

@ -0,0 +1,21 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
/**
* @immutable
*/
final readonly class ClassUnit extends CodeUnit
{
public function isClass(): bool
{
return true;
}
}

View File

@ -0,0 +1,524 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
use function count;
use function file;
use function file_exists;
use function is_readable;
use function range;
use function sprintf;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
/**
* @immutable
*/
abstract readonly class CodeUnit
{
/**
* @var non-empty-string
*/
private string $name;
/**
* @var non-empty-string
*/
private string $sourceFileName;
/**
* @var list<int>
*/
private array $sourceLines;
/**
* @param class-string $className
*
* @throws InvalidCodeUnitException
* @throws ReflectionException
*/
public static function forClass(string $className): ClassUnit
{
self::ensureUserDefinedClass($className);
$reflector = new ReflectionClass($className);
return new ClassUnit(
$className,
// @phpstan-ignore argument.type
$reflector->getFileName(),
range(
// @phpstan-ignore argument.type
$reflector->getStartLine(),
// @phpstan-ignore argument.type
$reflector->getEndLine(),
),
);
}
/**
* @param class-string $className
*
* @throws InvalidCodeUnitException
* @throws ReflectionException
*/
public static function forClassMethod(string $className, string $methodName): ClassMethodUnit
{
self::ensureUserDefinedClass($className);
$reflector = self::reflectorForClassMethod($className, $methodName);
return new ClassMethodUnit(
$className . '::' . $methodName,
// @phpstan-ignore argument.type
$reflector->getFileName(),
range(
// @phpstan-ignore argument.type
$reflector->getStartLine(),
// @phpstan-ignore argument.type
$reflector->getEndLine(),
),
);
}
/**
* @param non-empty-string $path
*
* @throws InvalidCodeUnitException
*/
public static function forFileWithAbsolutePath(string $path): FileUnit
{
self::ensureFileExistsAndIsReadable($path);
return new FileUnit(
$path,
$path,
range(
1,
// @phpstan-ignore argument.type
count(file($path)),
),
);
}
/**
* @param class-string $interfaceName
*
* @throws InvalidCodeUnitException
* @throws ReflectionException
*/
public static function forInterface(string $interfaceName): InterfaceUnit
{
self::ensureUserDefinedInterface($interfaceName);
$reflector = new ReflectionClass($interfaceName);
return new InterfaceUnit(
$interfaceName,
// @phpstan-ignore argument.type
$reflector->getFileName(),
range(
// @phpstan-ignore argument.type
$reflector->getStartLine(),
// @phpstan-ignore argument.type
$reflector->getEndLine(),
),
);
}
/**
* @param class-string $interfaceName
*
* @throws InvalidCodeUnitException
* @throws ReflectionException
*/
public static function forInterfaceMethod(string $interfaceName, string $methodName): InterfaceMethodUnit
{
self::ensureUserDefinedInterface($interfaceName);
$reflector = self::reflectorForClassMethod($interfaceName, $methodName);
return new InterfaceMethodUnit(
$interfaceName . '::' . $methodName,
// @phpstan-ignore argument.type
$reflector->getFileName(),
range(
// @phpstan-ignore argument.type
$reflector->getStartLine(),
// @phpstan-ignore argument.type
$reflector->getEndLine(),
),
);
}
/**
* @param class-string $traitName
*
* @throws InvalidCodeUnitException
* @throws ReflectionException
*/
public static function forTrait(string $traitName): TraitUnit
{
self::ensureUserDefinedTrait($traitName);
$reflector = new ReflectionClass($traitName);
return new TraitUnit(
$traitName,
// @phpstan-ignore argument.type
$reflector->getFileName(),
range(
// @phpstan-ignore argument.type
$reflector->getStartLine(),
// @phpstan-ignore argument.type
$reflector->getEndLine(),
),
);
}
/**
* @param class-string $traitName
*
* @throws InvalidCodeUnitException
* @throws ReflectionException
*/
public static function forTraitMethod(string $traitName, string $methodName): TraitMethodUnit
{
self::ensureUserDefinedTrait($traitName);
$reflector = self::reflectorForClassMethod($traitName, $methodName);
return new TraitMethodUnit(
$traitName . '::' . $methodName,
// @phpstan-ignore argument.type
$reflector->getFileName(),
range(
// @phpstan-ignore argument.type
$reflector->getStartLine(),
// @phpstan-ignore argument.type
$reflector->getEndLine(),
),
);
}
/**
* @param callable-string $functionName
*
* @throws InvalidCodeUnitException
* @throws ReflectionException
*/
public static function forFunction(string $functionName): FunctionUnit
{
$reflector = self::reflectorForFunction($functionName);
if (!$reflector->isUserDefined()) {
throw new InvalidCodeUnitException(
sprintf(
'"%s" is not a user-defined function',
$functionName,
),
);
}
return new FunctionUnit(
// @phpstan-ignore argument.type
$functionName,
// @phpstan-ignore argument.type
$reflector->getFileName(),
range(
// @phpstan-ignore argument.type
$reflector->getStartLine(),
// @phpstan-ignore argument.type
$reflector->getEndLine(),
),
);
}
/**
* @param non-empty-string $name
* @param non-empty-string $sourceFileName
* @param list<int> $sourceLines
*/
private function __construct(string $name, string $sourceFileName, array $sourceLines)
{
$this->name = $name;
$this->sourceFileName = $sourceFileName;
$this->sourceLines = $sourceLines;
}
/**
* @return non-empty-string
*/
public function name(): string
{
return $this->name;
}
/**
* @return non-empty-string
*/
public function sourceFileName(): string
{
return $this->sourceFileName;
}
/**
* @return list<int>
*/
public function sourceLines(): array
{
return $this->sourceLines;
}
/**
* @phpstan-assert-if-true ClassUnit $this
*/
public function isClass(): bool
{
return false;
}
/**
* @phpstan-assert-if-true ClassMethodUnit $this
*/
public function isClassMethod(): bool
{
return false;
}
/**
* @phpstan-assert-if-true InterfaceUnit $this
*/
public function isInterface(): bool
{
return false;
}
/**
* @phpstan-assert-if-true InterfaceMethodUnit $this
*/
public function isInterfaceMethod(): bool
{
return false;
}
/**
* @phpstan-assert-if-true TraitUnit $this
*/
public function isTrait(): bool
{
return false;
}
/**
* @phpstan-assert-if-true TraitMethodUnit $this
*/
public function isTraitMethod(): bool
{
return false;
}
/**
* @phpstan-assert-if-true FunctionUnit $this
*/
public function isFunction(): bool
{
return false;
}
/**
* @phpstan-assert-if-true FileUnit $this
*/
public function isFile(): bool
{
return false;
}
/**
* @param non-empty-string $path
*
* @throws InvalidCodeUnitException
*/
private static function ensureFileExistsAndIsReadable(string $path): void
{
if (!(file_exists($path) && is_readable($path))) {
throw new InvalidCodeUnitException(
sprintf(
'File "%s" does not exist or is not readable',
$path,
),
);
}
}
/**
* @param class-string $className
*
* @throws InvalidCodeUnitException
*/
private static function ensureUserDefinedClass(string $className): void
{
try {
$reflector = new ReflectionClass($className);
if ($reflector->isInterface()) {
throw new InvalidCodeUnitException(
sprintf(
'"%s" is an interface and not a class',
$className,
),
);
}
if ($reflector->isTrait()) {
throw new InvalidCodeUnitException(
sprintf(
'"%s" is a trait and not a class',
$className,
),
);
}
if (!$reflector->isUserDefined()) {
throw new InvalidCodeUnitException(
sprintf(
'"%s" is not a user-defined class',
$className,
),
);
}
// @codeCoverageIgnoreStart
} catch (\ReflectionException $e) {
throw new ReflectionException(
$e->getMessage(),
$e->getCode(),
$e,
);
}
// @codeCoverageIgnoreEnd
}
/**
* @param class-string $interfaceName
*
* @throws InvalidCodeUnitException
*/
private static function ensureUserDefinedInterface(string $interfaceName): void
{
try {
$reflector = new ReflectionClass($interfaceName);
if (!$reflector->isInterface()) {
throw new InvalidCodeUnitException(
sprintf(
'"%s" is not an interface',
$interfaceName,
),
);
}
if (!$reflector->isUserDefined()) {
throw new InvalidCodeUnitException(
sprintf(
'"%s" is not a user-defined interface',
$interfaceName,
),
);
}
// @codeCoverageIgnoreStart
} catch (\ReflectionException $e) {
throw new ReflectionException(
$e->getMessage(),
$e->getCode(),
$e,
);
}
// @codeCoverageIgnoreEnd
}
/**
* @param class-string $traitName
*
* @throws InvalidCodeUnitException
*/
private static function ensureUserDefinedTrait(string $traitName): void
{
try {
$reflector = new ReflectionClass($traitName);
if (!$reflector->isTrait()) {
throw new InvalidCodeUnitException(
sprintf(
'"%s" is not a trait',
$traitName,
),
);
}
// @codeCoverageIgnoreStart
if (!$reflector->isUserDefined()) {
throw new InvalidCodeUnitException(
sprintf(
'"%s" is not a user-defined trait',
$traitName,
),
);
}
} catch (\ReflectionException $e) {
throw new ReflectionException(
$e->getMessage(),
$e->getCode(),
$e,
);
}
// @codeCoverageIgnoreEnd
}
/**
* @param class-string $className
*
* @throws ReflectionException
*/
private static function reflectorForClassMethod(string $className, string $methodName): ReflectionMethod
{
try {
return new ReflectionMethod($className, $methodName);
// @codeCoverageIgnoreStart
} catch (\ReflectionException $e) {
throw new ReflectionException(
$e->getMessage(),
$e->getCode(),
$e,
);
}
// @codeCoverageIgnoreEnd
}
/**
* @param callable-string $functionName
*
* @throws ReflectionException
*/
private static function reflectorForFunction(string $functionName): ReflectionFunction
{
try {
return new ReflectionFunction($functionName);
// @codeCoverageIgnoreStart
} catch (\ReflectionException $e) {
throw new ReflectionException(
$e->getMessage(),
$e->getCode(),
$e,
);
}
// @codeCoverageIgnoreEnd
}
}

View File

@ -0,0 +1,75 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
use function array_merge;
use function count;
use Countable;
use IteratorAggregate;
/**
* @template-implements IteratorAggregate<int, CodeUnit>
*
* @immutable
*/
final readonly class CodeUnitCollection implements Countable, IteratorAggregate
{
/**
* @var list<CodeUnit>
*/
private array $codeUnits;
public static function fromList(CodeUnit ...$codeUnits): self
{
// @phpstan-ignore argument.type
return new self($codeUnits);
}
/**
* @param list<CodeUnit> $codeUnits
*/
private function __construct(array $codeUnits)
{
$this->codeUnits = $codeUnits;
}
/**
* @return list<CodeUnit>
*/
public function asArray(): array
{
return $this->codeUnits;
}
public function getIterator(): CodeUnitCollectionIterator
{
return new CodeUnitCollectionIterator($this);
}
public function count(): int
{
return count($this->codeUnits);
}
public function isEmpty(): bool
{
return empty($this->codeUnits);
}
public function mergeWith(self $other): self
{
return new self(
array_merge(
$this->asArray(),
$other->asArray(),
),
);
}
}

View File

@ -0,0 +1,54 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
use Iterator;
/**
* @template-implements Iterator<int, CodeUnit>
*/
final class CodeUnitCollectionIterator implements Iterator
{
/**
* @var list<CodeUnit>
*/
private array $codeUnits;
private int $position = 0;
public function __construct(CodeUnitCollection $collection)
{
$this->codeUnits = $collection->asArray();
}
public function rewind(): void
{
$this->position = 0;
}
public function valid(): bool
{
return isset($this->codeUnits[$this->position]);
}
public function key(): int
{
return $this->position;
}
public function current(): CodeUnit
{
return $this->codeUnits[$this->position];
}
public function next(): void
{
$this->position++;
}
}

View File

@ -0,0 +1,21 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
/**
* @immutable
*/
final readonly class FileUnit extends CodeUnit
{
public function isFile(): bool
{
return true;
}
}

View File

@ -0,0 +1,21 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
/**
* @immutable
*/
final readonly class FunctionUnit extends CodeUnit
{
public function isFunction(): bool
{
return true;
}
}

View File

@ -0,0 +1,21 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
/**
* @immutable
*/
final readonly class InterfaceMethodUnit extends CodeUnit
{
public function isInterfaceMethod(): bool
{
return true;
}
}

View File

@ -0,0 +1,21 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
/**
* @immutable
*/
final readonly class InterfaceUnit extends CodeUnit
{
public function isInterface(): bool
{
return true;
}
}

View File

@ -0,0 +1,185 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
use function array_keys;
use function array_merge;
use function array_unique;
use function array_values;
use function class_exists;
use function explode;
use function function_exists;
use function interface_exists;
use function ksort;
use function method_exists;
use function sort;
use function sprintf;
use function str_contains;
use function trait_exists;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
final class Mapper
{
/**
* @return array<string,list<int>>
*/
public function codeUnitsToSourceLines(CodeUnitCollection $codeUnits): array
{
$result = [];
foreach ($codeUnits as $codeUnit) {
$sourceFileName = $codeUnit->sourceFileName();
if (!isset($result[$sourceFileName])) {
$result[$sourceFileName] = [];
}
$result[$sourceFileName] = array_merge($result[$sourceFileName], $codeUnit->sourceLines());
}
foreach (array_keys($result) as $sourceFileName) {
$result[$sourceFileName] = array_values(array_unique($result[$sourceFileName]));
sort($result[$sourceFileName]);
}
ksort($result);
return $result;
}
/**
* @throws InvalidCodeUnitException
* @throws ReflectionException
*/
public function stringToCodeUnits(string $unit): CodeUnitCollection
{
if (str_contains($unit, '::')) {
[$firstPart, $secondPart] = explode('::', $unit);
if ($this->isUserDefinedFunction($secondPart)) {
return CodeUnitCollection::fromList(CodeUnit::forFunction($secondPart));
}
if ($this->isUserDefinedMethod($firstPart, $secondPart)) {
return CodeUnitCollection::fromList(CodeUnit::forClassMethod($firstPart, $secondPart));
}
if ($this->isUserDefinedInterface($firstPart)) {
return CodeUnitCollection::fromList(CodeUnit::forInterfaceMethod($firstPart, $secondPart));
}
if ($this->isUserDefinedTrait($firstPart)) {
return CodeUnitCollection::fromList(CodeUnit::forTraitMethod($firstPart, $secondPart));
}
} else {
if ($this->isUserDefinedClass($unit)) {
$units = [CodeUnit::forClass($unit)];
foreach ((new ReflectionClass($unit))->getTraits() as $trait) {
if (!$trait->isUserDefined()) {
// @codeCoverageIgnoreStart
continue;
// @codeCoverageIgnoreEnd
}
$units[] = CodeUnit::forTrait($trait->getName());
}
return CodeUnitCollection::fromList(...$units);
}
if ($this->isUserDefinedInterface($unit)) {
return CodeUnitCollection::fromList(CodeUnit::forInterface($unit));
}
if ($this->isUserDefinedTrait($unit)) {
return CodeUnitCollection::fromList(CodeUnit::forTrait($unit));
}
if ($this->isUserDefinedFunction($unit)) {
return CodeUnitCollection::fromList(CodeUnit::forFunction($unit));
}
}
throw new InvalidCodeUnitException(
sprintf(
'"%s" is not a valid code unit',
$unit,
),
);
}
/**
* @phpstan-assert-if-true callable-string $functionName
*/
private function isUserDefinedFunction(string $functionName): bool
{
if (!function_exists($functionName)) {
return false;
}
return (new ReflectionFunction($functionName))->isUserDefined();
}
/**
* @phpstan-assert-if-true class-string $className
*/
private function isUserDefinedClass(string $className): bool
{
if (!class_exists($className)) {
return false;
}
return (new ReflectionClass($className))->isUserDefined();
}
/**
* @phpstan-assert-if-true interface-string $interfaceName
*/
private function isUserDefinedInterface(string $interfaceName): bool
{
if (!interface_exists($interfaceName)) {
return false;
}
return (new ReflectionClass($interfaceName))->isUserDefined();
}
/**
* @phpstan-assert-if-true trait-string $traitName
*/
private function isUserDefinedTrait(string $traitName): bool
{
if (!trait_exists($traitName)) {
return false;
}
return (new ReflectionClass($traitName))->isUserDefined();
}
/**
* @phpstan-assert-if-true class-string $className
*/
private function isUserDefinedMethod(string $className, string $methodName): bool
{
if (!class_exists($className)) {
return false;
}
if (!method_exists($className, $methodName)) {
return false;
}
return (new ReflectionMethod($className, $methodName))->isUserDefined();
}
}

View File

@ -0,0 +1,21 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
/**
* @immutable
*/
final readonly class TraitMethodUnit extends CodeUnit
{
public function isTraitMethod(): bool
{
return true;
}
}

View File

@ -0,0 +1,21 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
/**
* @immutable
*/
final readonly class TraitUnit extends CodeUnit
{
public function isTrait(): bool
{
return true;
}
}

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
use Throwable;
interface Exception extends Throwable
{
}

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
use RuntimeException;
final class InvalidCodeUnitException extends RuntimeException implements Exception
{
}

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
use RuntimeException;
final class NoTraitException extends RuntimeException implements Exception
{
}

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/code-unit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeUnit;
use RuntimeException;
final class ReflectionException extends RuntimeException implements Exception
{
}

195
vendor/sebastian/comparator/ChangeLog.md vendored Normal file
View File

@ -0,0 +1,195 @@
# ChangeLog
All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [6.0.2] - 2024-08-12
### Fixed
* [#112](https://github.com/sebastianbergmann/comparator/issues/112): Arrays with different keys and the same values are considered equal in canonicalize mode
## [6.0.1] - 2024-07-03
### Changed
* This project now uses PHPStan instead of Psalm for static analysis
## [6.0.0] - 2024-02-02
### Removed
* Removed support for PHP 8.1
## [5.0.2] - 2024-08-12
### Fixed
* [#112](https://github.com/sebastianbergmann/comparator/issues/112): Arrays with different keys and the same values are considered equal in canonicalize mode
## [5.0.1] - 2023-08-14
### Fixed
* `MockObjectComparator` only works on instances of `PHPUnit\Framework\MockObject\MockObject`, but not on instances of `PHPUnit\Framework\MockObject\Stub`
* `MockObjectComparator` only ignores the `$__phpunit_invocationMocker` property, but not other properties with names prefixed with `__phpunit_`
## [5.0.0] - 2023-02-03
### Changed
* Methods now have parameter and return type declarations
* `Comparator::$factory` is now private, use `Comparator::factory()` instead
* `ComparisonFailure`, `DOMNodeComparator`, `DateTimeComparator`, `ExceptionComparator`, `MockObjectComparator`, `NumericComparator`, `ResourceComparator`, `SplObjectStorageComparator`, and `TypeComparator` are now `final`
* `ScalarComparator` and `DOMNodeComparator` now use `mb_strtolower($string, 'UTF-8')` instead of `strtolower($string)`
### Removed
* Removed `$identical` parameter from `ComparisonFailure::__construct()`
* Removed `Comparator::$exporter`
* Removed support for PHP 7.3, PHP 7.4, and PHP 8.0
## [4.0.8] - 2022-09-14
### Fixed
* [#102](https://github.com/sebastianbergmann/comparator/pull/102): Fix `float` comparison precision
## [4.0.7] - 2022-09-14
### Fixed
* [#99](https://github.com/sebastianbergmann/comparator/pull/99): Fix weak comparison between `'0'` and `false`
## [4.0.6] - 2020-10-26
### Fixed
* `SebastianBergmann\Comparator\Exception` now correctly extends `\Throwable`
## [4.0.5] - 2020-09-30
### Fixed
* [#89](https://github.com/sebastianbergmann/comparator/pull/89): Handle PHP 8 `ValueError`
## [4.0.4] - 2020-09-28
### Changed
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [4.0.3] - 2020-06-26
### Added
* This component is now supported on PHP 8
## [4.0.2] - 2020-06-15
### Fixed
* [#85](https://github.com/sebastianbergmann/comparator/issues/85): Version 4.0.1 breaks backward compatibility
## [4.0.1] - 2020-06-15
### Changed
* Tests etc. are now ignored for archive exports
## [4.0.0] - 2020-02-07
### Removed
* Removed support for PHP 7.1 and PHP 7.2
## [3.0.5] - 2022-09-14
### Fixed
* [#102](https://github.com/sebastianbergmann/comparator/pull/102): Fix `float` comparison precision
## [3.0.4] - 2022-09-14
### Fixed
* [#99](https://github.com/sebastianbergmann/comparator/pull/99): Fix weak comparison between `'0'` and `false`
## [3.0.3] - 2020-11-30
### Changed
* Changed PHP version constraint in `composer.json` from `^7.1` to `>=7.1`
## [3.0.2] - 2018-07-12
### Changed
* By default, `MockObjectComparator` is now tried before all other (default) comparators
## [3.0.1] - 2018-06-14
### Fixed
* [#53](https://github.com/sebastianbergmann/comparator/pull/53): `DOMNodeComparator` ignores `$ignoreCase` parameter
* [#58](https://github.com/sebastianbergmann/comparator/pull/58): `ScalarComparator` does not handle extremely ugly string comparison edge cases
## [3.0.0] - 2018-04-18
### Fixed
* [#48](https://github.com/sebastianbergmann/comparator/issues/48): `DateTimeComparator` does not support fractional second deltas
### Removed
* Removed support for PHP 7.0
## [2.1.3] - 2018-02-01
### Changed
* This component is now compatible with version 3 of `sebastian/diff`
## [2.1.2] - 2018-01-12
### Fixed
* Fix comparison of `DateTimeImmutable` objects
## [2.1.1] - 2017-12-22
### Fixed
* [phpunit/#2923](https://github.com/sebastianbergmann/phpunit/issues/2923): Unexpected failed date matching
## [2.1.0] - 2017-11-03
### Added
* Added `SebastianBergmann\Comparator\Factory::reset()` to unregister all non-default comparators
* Added support for `phpunit/phpunit-mock-objects` version `^5.0`
[6.0.2]: https://github.com/sebastianbergmann/comparator/compare/6.0.1...6.0.2
[6.0.1]: https://github.com/sebastianbergmann/comparator/compare/6.0.0...6.0.1
[6.0.0]: https://github.com/sebastianbergmann/comparator/compare/5.0...6.0.0
[5.0.2]: https://github.com/sebastianbergmann/comparator/compare/5.0.1...5.0.2
[5.0.1]: https://github.com/sebastianbergmann/comparator/compare/5.0.0...5.0.1
[5.0.0]: https://github.com/sebastianbergmann/comparator/compare/4.0.8...5.0.0
[4.0.8]: https://github.com/sebastianbergmann/comparator/compare/4.0.7...4.0.8
[4.0.7]: https://github.com/sebastianbergmann/comparator/compare/4.0.6...4.0.7
[4.0.6]: https://github.com/sebastianbergmann/comparator/compare/4.0.5...4.0.6
[4.0.5]: https://github.com/sebastianbergmann/comparator/compare/4.0.4...4.0.5
[4.0.4]: https://github.com/sebastianbergmann/comparator/compare/4.0.3...4.0.4
[4.0.3]: https://github.com/sebastianbergmann/comparator/compare/4.0.2...4.0.3
[4.0.2]: https://github.com/sebastianbergmann/comparator/compare/4.0.1...4.0.2
[4.0.1]: https://github.com/sebastianbergmann/comparator/compare/4.0.0...4.0.1
[4.0.0]: https://github.com/sebastianbergmann/comparator/compare/3.0.5...4.0.0
[3.0.5]: https://github.com/sebastianbergmann/comparator/compare/3.0.4...3.0.5
[3.0.4]: https://github.com/sebastianbergmann/comparator/compare/3.0.3...3.0.4
[3.0.3]: https://github.com/sebastianbergmann/comparator/compare/3.0.2...3.0.3
[3.0.2]: https://github.com/sebastianbergmann/comparator/compare/3.0.1...3.0.2
[3.0.1]: https://github.com/sebastianbergmann/comparator/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/sebastianbergmann/comparator/compare/2.1.3...3.0.0
[2.1.3]: https://github.com/sebastianbergmann/comparator/compare/2.1.2...2.1.3
[2.1.2]: https://github.com/sebastianbergmann/comparator/compare/2.1.1...2.1.2
[2.1.1]: https://github.com/sebastianbergmann/comparator/compare/2.1.0...2.1.1
[2.1.0]: https://github.com/sebastianbergmann/comparator/compare/2.0.2...2.1.0

29
vendor/sebastian/comparator/LICENSE vendored Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2002-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

42
vendor/sebastian/comparator/README.md vendored Normal file
View File

@ -0,0 +1,42 @@
[![Latest Stable Version](https://poser.pugx.org/sebastian/comparator/v)](https://packagist.org/packages/sebastian/comparator)
[![CI Status](https://github.com/sebastianbergmann/comparator/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/comparator/actions)
[![codecov](https://codecov.io/gh/sebastianbergmann/comparator/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/comparator)
# sebastian/comparator
This component provides the functionality to compare PHP values for equality.
## Installation
You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/):
```
composer require sebastian/comparator
```
If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:
```
composer require --dev sebastian/comparator
```
## Usage
```php
<?php
use SebastianBergmann\Comparator\Factory;
use SebastianBergmann\Comparator\ComparisonFailure;
$date1 = new DateTime('2013-03-29 04:13:35', new DateTimeZone('America/New_York'));
$date2 = new DateTime('2013-03-29 03:13:35', new DateTimeZone('America/Chicago'));
$factory = new Factory;
$comparator = $factory->getComparatorFor($date1, $date2);
try {
$comparator->assertEquals($date1, $date2);
print "Dates match";
} catch (ComparisonFailure $failure) {
print "Dates don't match";
}
```

30
vendor/sebastian/comparator/SECURITY.md vendored Normal file
View File

@ -0,0 +1,30 @@
# Security Policy
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email `sebastian@phpunit.de`.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
## Web Context
The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit.
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.

View File

@ -0,0 +1,63 @@
{
"name": "sebastian/comparator",
"description": "Provides the functionality to compare PHP values for equality",
"keywords": ["comparator","compare","equality"],
"homepage": "https://github.com/sebastianbergmann/comparator",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
}
],
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy"
},
"prefer-stable": true,
"require": {
"php": ">=8.2",
"sebastian/diff": "^6.0",
"sebastian/exporter": "^6.0",
"ext-dom": "*",
"ext-mbstring": "*"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
},
"config": {
"platform": {
"php": "8.2.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"autoload": {
"classmap": [
"src/"
]
},
"autoload-dev": {
"classmap": [
"tests/_fixture"
]
},
"extra": {
"branch-alias": {
"dev-main": "6.0-dev"
}
}
}

View File

@ -0,0 +1,137 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
use function array_is_list;
use function array_key_exists;
use function assert;
use function is_array;
use function sort;
use function sprintf;
use function str_replace;
use function trim;
use SebastianBergmann\Exporter\Exporter;
/**
* Arrays are equal if they contain the same key-value pairs.
* The order of the keys does not matter.
* The types of key-value pairs do not matter.
*/
class ArrayComparator extends Comparator
{
public function accepts(mixed $expected, mixed $actual): bool
{
return is_array($expected) && is_array($actual);
}
/**
* @param array<mixed> $processed
*
* @throws ComparisonFailure
*/
public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false, array &$processed = []): void
{
assert(is_array($expected));
assert(is_array($actual));
if ($canonicalize) {
if (array_is_list($expected)) {
sort($expected);
}
if (array_is_list($actual)) {
sort($actual);
}
}
$remaining = $actual;
$actualAsString = "Array (\n";
$expectedAsString = "Array (\n";
$equal = true;
$exporter = new Exporter;
foreach ($expected as $key => $value) {
unset($remaining[$key]);
if (!array_key_exists($key, $actual)) {
$expectedAsString .= sprintf(
" %s => %s\n",
$exporter->export($key),
$exporter->shortenedExport($value),
);
$equal = false;
continue;
}
try {
$comparator = $this->factory()->getComparatorFor($value, $actual[$key]);
/** @phpstan-ignore arguments.count */
$comparator->assertEquals($value, $actual[$key], $delta, $canonicalize, $ignoreCase, $processed);
$expectedAsString .= sprintf(
" %s => %s\n",
$exporter->export($key),
$exporter->shortenedExport($value),
);
$actualAsString .= sprintf(
" %s => %s\n",
$exporter->export($key),
$exporter->shortenedExport($actual[$key]),
);
} catch (ComparisonFailure $e) {
$expectedAsString .= sprintf(
" %s => %s\n",
$exporter->export($key),
$e->getExpectedAsString() ? $this->indent($e->getExpectedAsString()) : $exporter->shortenedExport($e->getExpected()),
);
$actualAsString .= sprintf(
" %s => %s\n",
$exporter->export($key),
$e->getActualAsString() ? $this->indent($e->getActualAsString()) : $exporter->shortenedExport($e->getActual()),
);
$equal = false;
}
}
foreach ($remaining as $key => $value) {
$actualAsString .= sprintf(
" %s => %s\n",
$exporter->export($key),
$exporter->shortenedExport($value),
);
$equal = false;
}
$expectedAsString .= ')';
$actualAsString .= ')';
if (!$equal) {
throw new ComparisonFailure(
$expected,
$actual,
$expectedAsString,
$actualAsString,
'Failed asserting that two arrays are equal.',
);
}
}
private function indent(string $lines): string
{
return trim(str_replace("\n", "\n ", $lines));
}
}

View File

@ -0,0 +1,32 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
abstract class Comparator
{
private Factory $factory;
public function setFactory(Factory $factory): void
{
$this->factory = $factory;
}
abstract public function accepts(mixed $expected, mixed $actual): bool;
/**
* @throws ComparisonFailure
*/
abstract public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false): void;
protected function factory(): Factory
{
return $this->factory;
}
}

View File

@ -0,0 +1,68 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
use RuntimeException;
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;
final class ComparisonFailure extends RuntimeException
{
private mixed $expected;
private mixed $actual;
private string $expectedAsString;
private string $actualAsString;
public function __construct(mixed $expected, mixed $actual, string $expectedAsString, string $actualAsString, string $message = '')
{
parent::__construct($message);
$this->expected = $expected;
$this->actual = $actual;
$this->expectedAsString = $expectedAsString;
$this->actualAsString = $actualAsString;
}
public function getActual(): mixed
{
return $this->actual;
}
public function getExpected(): mixed
{
return $this->expected;
}
public function getActualAsString(): string
{
return $this->actualAsString;
}
public function getExpectedAsString(): string
{
return $this->expectedAsString;
}
public function getDiff(): string
{
if (!$this->actualAsString && !$this->expectedAsString) {
return '';
}
$differ = new Differ(new UnifiedDiffOutputBuilder("\n--- Expected\n+++ Actual\n"));
return $differ->diff($this->expectedAsString, $this->actualAsString);
}
public function toString(): string
{
return $this->getMessage() . $this->getDiff();
}
}

View File

@ -0,0 +1,98 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
use function assert;
use function mb_strtolower;
use function sprintf;
use DOMDocument;
use DOMNode;
use ValueError;
final class DOMNodeComparator extends ObjectComparator
{
public function accepts(mixed $expected, mixed $actual): bool
{
return $expected instanceof DOMNode && $actual instanceof DOMNode;
}
/**
* @param array<mixed> $processed
*
* @throws ComparisonFailure
*/
public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false, array &$processed = []): void
{
assert($expected instanceof DOMNode);
assert($actual instanceof DOMNode);
$expectedAsString = $this->nodeToText($expected, true, $ignoreCase);
$actualAsString = $this->nodeToText($actual, true, $ignoreCase);
if ($expectedAsString !== $actualAsString) {
$type = $expected instanceof DOMDocument ? 'documents' : 'nodes';
throw new ComparisonFailure(
$expected,
$actual,
$expectedAsString,
$actualAsString,
sprintf("Failed asserting that two DOM %s are equal.\n", $type),
);
}
}
/**
* Returns the normalized, whitespace-cleaned, and indented textual
* representation of a DOMNode.
*/
private function nodeToText(DOMNode $node, bool $canonicalize, bool $ignoreCase): string
{
if ($canonicalize) {
$document = new DOMDocument;
try {
$c14n = $node->C14N();
assert(!empty($c14n));
@$document->loadXML($c14n);
} catch (ValueError) {
}
$node = $document;
}
if ($node instanceof DOMDocument) {
$document = $node;
} else {
$document = $node->ownerDocument;
}
assert($document instanceof DOMDocument);
$document->formatOutput = true;
$document->normalizeDocument();
if ($node instanceof DOMDocument) {
$text = $node->saveXML();
} else {
$text = $document->saveXML($node);
}
assert($text !== false);
if ($ignoreCase) {
return mb_strtolower($text, 'UTF-8');
}
return $text;
}
}

View File

@ -0,0 +1,64 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
use function abs;
use function assert;
use function floor;
use function sprintf;
use DateInterval;
use DateTime;
use DateTimeImmutable;
use DateTimeZone;
final class DateTimeComparator extends ObjectComparator
{
public function accepts(mixed $expected, mixed $actual): bool
{
return ($expected instanceof DateTime || $expected instanceof DateTimeImmutable) &&
($actual instanceof DateTime || $actual instanceof DateTimeImmutable);
}
/**
* @param array<mixed> $processed
*
* @throws ComparisonFailure
*/
public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false, array &$processed = []): void
{
assert($expected instanceof DateTime || $expected instanceof DateTimeImmutable);
assert($actual instanceof DateTime || $actual instanceof DateTimeImmutable);
$absDelta = abs($delta);
$delta = new DateInterval(sprintf('PT%dS', $absDelta));
$delta->f = $absDelta - floor($absDelta);
$actualClone = (clone $actual)
->setTimezone(new DateTimeZone('UTC'));
$expectedLower = (clone $expected)
->setTimezone(new DateTimeZone('UTC'))
->sub($delta);
$expectedUpper = (clone $expected)
->setTimezone(new DateTimeZone('UTC'))
->add($delta);
if ($actualClone < $expectedLower || $actualClone > $expectedUpper) {
throw new ComparisonFailure(
$expected,
$actual,
$expected->format('Y-m-d\TH:i:s.uO'),
$actual->format('Y-m-d\TH:i:s.uO'),
'Failed asserting that two DateTime objects are equal.',
);
}
}
}

View File

@ -0,0 +1,44 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
use function assert;
use Exception;
/**
* Compares Exception instances for equality.
*/
final class ExceptionComparator extends ObjectComparator
{
public function accepts(mixed $expected, mixed $actual): bool
{
return $expected instanceof Exception && $actual instanceof Exception;
}
/**
* @return array<mixed>
*/
protected function toArray(object $object): array
{
assert($object instanceof Exception);
$array = parent::toArray($object);
unset(
$array['file'],
$array['line'],
$array['trace'],
$array['string'],
$array['xdebug_message'],
);
return $array;
}
}

View File

@ -0,0 +1,114 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
use function array_unshift;
final class Factory
{
private static ?Factory $instance = null;
/**
* @var list<Comparator>
*/
private array $customComparators = [];
/**
* @var list<Comparator>
*/
private array $defaultComparators = [];
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self; // @codeCoverageIgnore
}
return self::$instance;
}
public function __construct()
{
$this->registerDefaultComparators();
}
public function getComparatorFor(mixed $expected, mixed $actual): Comparator
{
foreach ($this->customComparators as $comparator) {
if ($comparator->accepts($expected, $actual)) {
return $comparator;
}
}
foreach ($this->defaultComparators as $comparator) {
if ($comparator->accepts($expected, $actual)) {
return $comparator;
}
}
throw new RuntimeException('No suitable Comparator implementation found');
}
/**
* Registers a new comparator.
*
* This comparator will be returned by getComparatorFor() if its accept() method
* returns TRUE for the compared values. It has higher priority than the
* existing comparators, meaning that its accept() method will be invoked
* before those of the other comparators.
*/
public function register(Comparator $comparator): void
{
array_unshift($this->customComparators, $comparator);
$comparator->setFactory($this);
}
/**
* Unregisters a comparator.
*
* This comparator will no longer be considered by getComparatorFor().
*/
public function unregister(Comparator $comparator): void
{
foreach ($this->customComparators as $key => $_comparator) {
if ($comparator === $_comparator) {
unset($this->customComparators[$key]);
}
}
}
public function reset(): void
{
$this->customComparators = [];
}
private function registerDefaultComparators(): void
{
$this->registerDefaultComparator(new MockObjectComparator);
$this->registerDefaultComparator(new DateTimeComparator);
$this->registerDefaultComparator(new DOMNodeComparator);
$this->registerDefaultComparator(new SplObjectStorageComparator);
$this->registerDefaultComparator(new ExceptionComparator);
$this->registerDefaultComparator(new ObjectComparator);
$this->registerDefaultComparator(new ResourceComparator);
$this->registerDefaultComparator(new ArrayComparator);
$this->registerDefaultComparator(new NumericComparator);
$this->registerDefaultComparator(new ScalarComparator);
$this->registerDefaultComparator(new TypeComparator);
}
private function registerDefaultComparator(Comparator $comparator): void
{
$this->defaultComparators[] = $comparator;
$comparator->setFactory($this);
}
}

View File

@ -0,0 +1,46 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
use function array_keys;
use function assert;
use function str_starts_with;
use PHPUnit\Framework\MockObject\Stub;
/**
* Compares PHPUnit\Framework\MockObject\MockObject instances for equality.
*/
final class MockObjectComparator extends ObjectComparator
{
public function accepts(mixed $expected, mixed $actual): bool
{
return $expected instanceof Stub && $actual instanceof Stub;
}
/**
* @return array<mixed>
*/
protected function toArray(object $object): array
{
assert($object instanceof Stub);
$array = parent::toArray($object);
foreach (array_keys($array) as $key) {
if (!str_starts_with($key, '__phpunit_')) {
continue;
}
unset($array[$key]);
}
return $array;
}
}

View File

@ -0,0 +1,67 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
use function abs;
use function is_float;
use function is_infinite;
use function is_nan;
use function is_numeric;
use function is_string;
use function sprintf;
use SebastianBergmann\Exporter\Exporter;
final class NumericComparator extends ScalarComparator
{
public function accepts(mixed $expected, mixed $actual): bool
{
// all numerical values, but not if both of them are strings
return is_numeric($expected) && is_numeric($actual) &&
!(is_string($expected) && is_string($actual));
}
/**
* @throws ComparisonFailure
*/
public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false): void
{
if ($this->isInfinite($actual) && $this->isInfinite($expected)) {
return;
}
if (($this->isInfinite($actual) xor $this->isInfinite($expected)) ||
($this->isNan($actual) || $this->isNan($expected)) ||
abs($actual - $expected) > $delta) {
$exporter = new Exporter;
throw new ComparisonFailure(
$expected,
$actual,
'',
'',
sprintf(
'Failed asserting that %s matches expected %s.',
$exporter->export($actual),
$exporter->export($expected),
),
);
}
}
private function isInfinite(mixed $value): bool
{
return is_float($value) && is_infinite($value);
}
private function isNan(mixed $value): bool
{
return is_float($value) && is_nan($value);
}
}

View File

@ -0,0 +1,93 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
use function assert;
use function in_array;
use function is_object;
use function sprintf;
use function substr_replace;
use SebastianBergmann\Exporter\Exporter;
class ObjectComparator extends ArrayComparator
{
public function accepts(mixed $expected, mixed $actual): bool
{
return is_object($expected) && is_object($actual);
}
/**
* @param array<mixed> $processed
*
* @throws ComparisonFailure
*/
public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false, array &$processed = []): void
{
assert(is_object($expected));
assert(is_object($actual));
if ($actual::class !== $expected::class) {
$exporter = new Exporter;
throw new ComparisonFailure(
$expected,
$actual,
$exporter->export($expected),
$exporter->export($actual),
sprintf(
'%s is not instance of expected class "%s".',
$exporter->export($actual),
$expected::class,
),
);
}
// don't compare twice to allow for cyclic dependencies
if (in_array([$actual, $expected], $processed, true) ||
in_array([$expected, $actual], $processed, true)) {
return;
}
$processed[] = [$actual, $expected];
// don't compare objects if they are identical
// this helps to avoid the error "maximum function nesting level reached"
// CAUTION: this conditional clause is not tested
if ($actual !== $expected) {
try {
parent::assertEquals(
$this->toArray($expected),
$this->toArray($actual),
$delta,
$canonicalize,
$ignoreCase,
$processed,
);
} catch (ComparisonFailure $e) {
throw new ComparisonFailure(
$expected,
$actual,
// replace "Array" with "MyClass object"
substr_replace($e->getExpectedAsString(), $expected::class . ' Object', 0, 5),
substr_replace($e->getActualAsString(), $actual::class . ' Object', 0, 5),
'Failed asserting that two objects are equal.',
);
}
}
}
/**
* @return array<mixed>
*/
protected function toArray(object $object): array
{
return (new Exporter)->toArray($object);
}
}

View File

@ -0,0 +1,42 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
use function assert;
use function is_resource;
use SebastianBergmann\Exporter\Exporter;
final class ResourceComparator extends Comparator
{
public function accepts(mixed $expected, mixed $actual): bool
{
return is_resource($expected) && is_resource($actual);
}
/**
* @throws ComparisonFailure
*/
public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false): void
{
assert(is_resource($expected));
assert(is_resource($actual));
$exporter = new Exporter;
if ($actual != $expected) {
throw new ComparisonFailure(
$expected,
$actual,
$exporter->export($expected),
$exporter->export($actual),
);
}
}
}

View File

@ -0,0 +1,84 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
use function is_bool;
use function is_object;
use function is_scalar;
use function is_string;
use function mb_strtolower;
use function method_exists;
use function sprintf;
use SebastianBergmann\Exporter\Exporter;
/**
* Compares scalar or NULL values for equality.
*/
class ScalarComparator extends Comparator
{
public function accepts(mixed $expected, mixed $actual): bool
{
return ((is_scalar($expected) xor null === $expected) &&
(is_scalar($actual) xor null === $actual)) ||
// allow comparison between strings and objects featuring __toString()
(is_string($expected) && is_object($actual) && method_exists($actual, '__toString')) ||
(is_object($expected) && method_exists($expected, '__toString') && is_string($actual));
}
/**
* @throws ComparisonFailure
*/
public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false): void
{
$expectedToCompare = $expected;
$actualToCompare = $actual;
$exporter = new Exporter;
// always compare as strings to avoid strange behaviour
// otherwise 0 == 'Foobar'
if ((is_string($expected) && !is_bool($actual)) || (is_string($actual) && !is_bool($expected))) {
/** @phpstan-ignore cast.string */
$expectedToCompare = (string) $expectedToCompare;
/** @phpstan-ignore cast.string */
$actualToCompare = (string) $actualToCompare;
if ($ignoreCase) {
$expectedToCompare = mb_strtolower($expectedToCompare, 'UTF-8');
$actualToCompare = mb_strtolower($actualToCompare, 'UTF-8');
}
}
if ($expectedToCompare !== $actualToCompare && is_string($expected) && is_string($actual)) {
throw new ComparisonFailure(
$expected,
$actual,
$exporter->export($expected),
$exporter->export($actual),
'Failed asserting that two strings are equal.',
);
}
if ($expectedToCompare != $actualToCompare) {
throw new ComparisonFailure(
$expected,
$actual,
// no diff is required
'',
'',
sprintf(
'Failed asserting that %s matches expected %s.',
$exporter->export($actual),
$exporter->export($expected),
),
);
}
}
}

View File

@ -0,0 +1,57 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
use function assert;
use SebastianBergmann\Exporter\Exporter;
use SplObjectStorage;
final class SplObjectStorageComparator extends Comparator
{
public function accepts(mixed $expected, mixed $actual): bool
{
return $expected instanceof SplObjectStorage && $actual instanceof SplObjectStorage;
}
/**
* @throws ComparisonFailure
*/
public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false): void
{
assert($expected instanceof SplObjectStorage);
assert($actual instanceof SplObjectStorage);
$exporter = new Exporter;
foreach ($actual as $object) {
if (!$expected->contains($object)) {
throw new ComparisonFailure(
$expected,
$actual,
$exporter->export($expected),
$exporter->export($actual),
'Failed asserting that two objects are equal.',
);
}
}
foreach ($expected as $object) {
if (!$actual->contains($object)) {
throw new ComparisonFailure(
$expected,
$actual,
$exporter->export($expected),
$exporter->export($actual),
'Failed asserting that two objects are equal.',
);
}
}
}
}

View File

@ -0,0 +1,43 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
use function gettype;
use function sprintf;
use SebastianBergmann\Exporter\Exporter;
final class TypeComparator extends Comparator
{
public function accepts(mixed $expected, mixed $actual): bool
{
return true;
}
/**
* @throws ComparisonFailure
*/
public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false): void
{
if (gettype($expected) != gettype($actual)) {
throw new ComparisonFailure(
$expected,
$actual,
// we don't need a diff
'',
'',
sprintf(
'%s does not match expected type "%s".',
(new Exporter)->shortenedExport($actual),
gettype($expected),
),
);
}
}
}

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
use Throwable;
interface Exception extends Throwable
{
}

View File

@ -0,0 +1,14 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/comparator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Comparator;
final class RuntimeException extends \RuntimeException implements Exception
{
}

View File

@ -0,0 +1,84 @@
# ChangeLog
All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
## [4.0.1] - 2024-07-03
### Changed
* This project now uses PHPStan instead of Psalm for static analysis
## [4.0.0] - 2024-02-02
### Removed
* This component now requires PHP-Parser 5
* This component is no longer supported on PHP 8.1
## [3.2.0] - 2023-12-21
### Added
* `ComplexityCollection::sortByDescendingCyclomaticComplexity()`
* Support for `match` arms
### Changed
* This component is now compatible with `nikic/php-parser` 5.0
## [3.1.0] - 2023-09-28
### Added
* `Complexity::isFunction()` and `Complexity::isMethod()`
* `ComplexityCollection::isFunction()` and `ComplexityCollection::isMethod()`
* `ComplexityCollection::mergeWith()`
### Fixed
* Anonymous classes are not processed correctly
## [3.0.1] - 2023-08-31
### Fixed
* [#7](https://github.com/sebastianbergmann/complexity/pull/7): `ComplexityCalculatingVisitor` tries to process interface methods
## [3.0.0] - 2023-02-03
### Removed
* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0
## [2.0.2] - 2020-10-26
### Fixed
* `SebastianBergmann\Complexity\Exception` now correctly extends `\Throwable`
## [2.0.1] - 2020-09-28
### Changed
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [2.0.0] - 2020-07-25
### Removed
* The `ParentConnectingVisitor` has been removed (it should have been marked as `@internal`)
## [1.0.0] - 2020-07-22
* Initial release
[4.0.1]: https://github.com/sebastianbergmann/complexity/compare/4.0.0...4.0.1
[4.0.0]: https://github.com/sebastianbergmann/complexity/compare/3.2...4.0.0
[3.2.0]: https://github.com/sebastianbergmann/complexity/compare/3.1.0...3.2.0
[3.1.0]: https://github.com/sebastianbergmann/complexity/compare/3.0.1...3.1.0
[3.0.1]: https://github.com/sebastianbergmann/complexity/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/sebastianbergmann/complexity/compare/2.0.2...3.0.0
[2.0.2]: https://github.com/sebastianbergmann/complexity/compare/2.0.1...2.0.2
[2.0.1]: https://github.com/sebastianbergmann/complexity/compare/2.0.0...2.0.1
[2.0.0]: https://github.com/sebastianbergmann/complexity/compare/1.0.0...2.0.0
[1.0.0]: https://github.com/sebastianbergmann/complexity/compare/70ee0ad32d9e2be3f85beffa3e2eb474193f2487...1.0.0

29
vendor/sebastian/complexity/LICENSE vendored Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2020-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21
vendor/sebastian/complexity/README.md vendored Normal file
View File

@ -0,0 +1,21 @@
[![Latest Stable Version](https://poser.pugx.org/sebastian/complexity/v/stable.png)](https://packagist.org/packages/sebastian/complexity)
[![CI Status](https://github.com/sebastianbergmann/complexity/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/complexity/actions)
[![codecov](https://codecov.io/gh/sebastianbergmann/complexity/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/complexity)
# sebastian/complexity
Library for calculating the complexity of PHP code units.
## Installation
You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/):
```
composer require sebastian/complexity
```
If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:
```
composer require --dev sebastian/complexity
```

30
vendor/sebastian/complexity/SECURITY.md vendored Normal file
View File

@ -0,0 +1,30 @@
# Security Policy
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email `sebastian@phpunit.de`.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
## Web Context
The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit.
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.

View File

@ -0,0 +1,43 @@
{
"name": "sebastian/complexity",
"description": "Library for calculating the complexity of PHP code units",
"type": "library",
"homepage": "https://github.com/sebastianbergmann/complexity",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"support": {
"issues": "https://github.com/sebastianbergmann/complexity/issues",
"security": "https://github.com/sebastianbergmann/complexity/security/policy"
},
"prefer-stable": true,
"require": {
"php": ">=8.2",
"nikic/php-parser": "^5.0"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
},
"config": {
"platform": {
"php": "8.2.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"autoload": {
"classmap": [
"src/"
]
},
"extra": {
"branch-alias": {
"dev-main": "4.0-dev"
}
}
}

View File

@ -0,0 +1,95 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/complexity.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Complexity;
use function assert;
use function file_exists;
use function file_get_contents;
use function is_readable;
use function is_string;
use PhpParser\Error;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\NodeVisitor\ParentConnectingVisitor;
use PhpParser\ParserFactory;
final class Calculator
{
/**
* @param non-empty-string $sourceFile
*
* @throws RuntimeException
*/
public function calculateForSourceFile(string $sourceFile): ComplexityCollection
{
assert(file_exists($sourceFile));
assert(is_readable($sourceFile));
$source = file_get_contents($sourceFile);
assert(is_string($source));
return $this->calculateForSourceString($source);
}
/**
* @throws RuntimeException
*/
public function calculateForSourceString(string $source): ComplexityCollection
{
try {
$nodes = (new ParserFactory)->createForHostVersion()->parse($source);
assert($nodes !== null);
return $this->calculateForAbstractSyntaxTree($nodes);
// @codeCoverageIgnoreStart
} catch (Error $error) {
throw new RuntimeException(
$error->getMessage(),
$error->getCode(),
$error,
);
}
// @codeCoverageIgnoreEnd
}
/**
* @param Node[] $nodes
*
* @throws RuntimeException
*/
public function calculateForAbstractSyntaxTree(array $nodes): ComplexityCollection
{
$traverser = new NodeTraverser;
$complexityCalculatingVisitor = new ComplexityCalculatingVisitor(true);
$traverser->addVisitor(new NameResolver);
$traverser->addVisitor(new ParentConnectingVisitor);
$traverser->addVisitor($complexityCalculatingVisitor);
try {
/* @noinspection UnusedFunctionResultInspection */
$traverser->traverse($nodes);
// @codeCoverageIgnoreStart
} catch (Error $error) {
throw new RuntimeException(
$error->getMessage(),
$error->getCode(),
$error,
);
}
// @codeCoverageIgnoreEnd
return $complexityCalculatingVisitor->result();
}
}

View File

@ -0,0 +1,64 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/complexity.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Complexity;
use function str_contains;
/**
* @immutable
*/
final readonly class Complexity
{
/**
* @var non-empty-string
*/
private string $name;
/**
* @var positive-int
*/
private int $cyclomaticComplexity;
/**
* @param non-empty-string $name
* @param positive-int $cyclomaticComplexity
*/
public function __construct(string $name, int $cyclomaticComplexity)
{
$this->name = $name;
$this->cyclomaticComplexity = $cyclomaticComplexity;
}
/**
* @return non-empty-string
*/
public function name(): string
{
return $this->name;
}
/**
* @return positive-int
*/
public function cyclomaticComplexity(): int
{
return $this->cyclomaticComplexity;
}
public function isFunction(): bool
{
return !$this->isMethod();
}
public function isMethod(): bool
{
return str_contains($this->name, '::');
}
}

View File

@ -0,0 +1,134 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/complexity.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Complexity;
use function array_filter;
use function array_merge;
use function array_reverse;
use function array_values;
use function count;
use function usort;
use Countable;
use IteratorAggregate;
/**
* @template-implements IteratorAggregate<int, Complexity>
*
* @psalm-immutable
*/
final readonly class ComplexityCollection implements Countable, IteratorAggregate
{
/**
* @var list<Complexity>
*/
private array $items;
public static function fromList(Complexity ...$items): self
{
return new self($items);
}
/**
* @param list<Complexity> $items
*/
private function __construct(array $items)
{
$this->items = $items;
}
/**
* @return list<Complexity>
*/
public function asArray(): array
{
return $this->items;
}
public function getIterator(): ComplexityCollectionIterator
{
return new ComplexityCollectionIterator($this);
}
/**
* @return non-negative-int
*/
public function count(): int
{
return count($this->items);
}
public function isEmpty(): bool
{
return empty($this->items);
}
/**
* @return non-negative-int
*/
public function cyclomaticComplexity(): int
{
$cyclomaticComplexity = 0;
foreach ($this as $item) {
$cyclomaticComplexity += $item->cyclomaticComplexity();
}
return $cyclomaticComplexity;
}
public function isFunction(): self
{
return new self(
array_values(
array_filter(
$this->items,
static fn (Complexity $complexity): bool => $complexity->isFunction(),
),
),
);
}
public function isMethod(): self
{
return new self(
array_values(
array_filter(
$this->items,
static fn (Complexity $complexity): bool => $complexity->isMethod(),
),
),
);
}
public function mergeWith(self $other): self
{
return new self(
array_merge(
$this->asArray(),
$other->asArray(),
),
);
}
public function sortByDescendingCyclomaticComplexity(): self
{
$items = $this->items;
usort(
$items,
static function (Complexity $a, Complexity $b): int
{
return $a->cyclomaticComplexity() <=> $b->cyclomaticComplexity();
},
);
return new self(array_reverse($items));
}
}

View File

@ -0,0 +1,54 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/complexity.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Complexity;
use Iterator;
/**
* @template-implements Iterator<int, Complexity>
*/
final class ComplexityCollectionIterator implements Iterator
{
/**
* @var list<Complexity>
*/
private readonly array $items;
private int $position = 0;
public function __construct(ComplexityCollection $items)
{
$this->items = $items->asArray();
}
public function rewind(): void
{
$this->position = 0;
}
public function valid(): bool
{
return isset($this->items[$this->position]);
}
public function key(): int
{
return $this->position;
}
public function current(): Complexity
{
return $this->items[$this->position];
}
public function next(): void
{
$this->position++;
}
}

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/complexity.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Complexity;
use Throwable;
interface Exception extends Throwable
{
}

View File

@ -0,0 +1,14 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/complexity.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Complexity;
final class RuntimeException extends \RuntimeException implements Exception
{
}

View File

@ -0,0 +1,133 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/complexity.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Complexity;
use function assert;
use function is_array;
use PhpParser\Node;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor;
use PhpParser\NodeVisitorAbstract;
final class ComplexityCalculatingVisitor extends NodeVisitorAbstract
{
/**
* @var list<Complexity>
*/
private array $result = [];
private bool $shortCircuitTraversal;
public function __construct(bool $shortCircuitTraversal)
{
$this->shortCircuitTraversal = $shortCircuitTraversal;
}
public function enterNode(Node $node): ?int
{
if (!$node instanceof ClassMethod && !$node instanceof Function_) {
return null;
}
if ($node instanceof ClassMethod) {
if ($node->getAttribute('parent') instanceof Interface_) {
return null;
}
if ($node->isAbstract()) {
return null;
}
$name = $this->classMethodName($node);
} else {
$name = $this->functionName($node);
}
$statements = $node->getStmts();
assert(is_array($statements));
$this->result[] = new Complexity(
$name,
$this->cyclomaticComplexity($statements),
);
if ($this->shortCircuitTraversal) {
return NodeVisitor::DONT_TRAVERSE_CHILDREN;
}
return null;
}
public function result(): ComplexityCollection
{
return ComplexityCollection::fromList(...$this->result);
}
/**
* @param Stmt[] $statements
*
* @return positive-int
*/
private function cyclomaticComplexity(array $statements): int
{
$traverser = new NodeTraverser;
$cyclomaticComplexityCalculatingVisitor = new CyclomaticComplexityCalculatingVisitor;
$traverser->addVisitor($cyclomaticComplexityCalculatingVisitor);
/* @noinspection UnusedFunctionResultInspection */
$traverser->traverse($statements);
return $cyclomaticComplexityCalculatingVisitor->cyclomaticComplexity();
}
/**
* @return non-empty-string
*/
private function classMethodName(ClassMethod $node): string
{
$parent = $node->getAttribute('parent');
assert($parent instanceof Class_ || $parent instanceof Trait_);
if ($parent->getAttribute('parent') instanceof New_) {
return 'anonymous class';
}
assert(isset($parent->namespacedName));
assert($parent->namespacedName instanceof Name);
return $parent->namespacedName->toString() . '::' . $node->name->toString();
}
/**
* @return non-empty-string
*/
private function functionName(Function_ $node): string
{
assert(isset($node->namespacedName));
assert($node->namespacedName instanceof Name);
$functionName = $node->namespacedName->toString();
assert($functionName !== '');
return $functionName;
}
}

View File

@ -0,0 +1,61 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/complexity.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Complexity;
use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
use PhpParser\Node\Expr\BinaryOp\LogicalAnd;
use PhpParser\Node\Expr\BinaryOp\LogicalOr;
use PhpParser\Node\Expr\Ternary;
use PhpParser\Node\Stmt\Case_;
use PhpParser\Node\Stmt\Catch_;
use PhpParser\Node\Stmt\ElseIf_;
use PhpParser\Node\Stmt\For_;
use PhpParser\Node\Stmt\Foreach_;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\While_;
use PhpParser\NodeVisitorAbstract;
final class CyclomaticComplexityCalculatingVisitor extends NodeVisitorAbstract
{
/**
* @var positive-int
*/
private int $cyclomaticComplexity = 1;
public function enterNode(Node $node): void
{
switch ($node::class) {
case BooleanAnd::class:
case BooleanOr::class:
case Case_::class:
case Catch_::class:
case ElseIf_::class:
case For_::class:
case Foreach_::class:
case If_::class:
case LogicalAnd::class:
case LogicalOr::class:
case Node\MatchArm::class:
case Ternary::class:
case While_::class:
$this->cyclomaticComplexity++;
}
}
/**
* @return positive-int
*/
public function cyclomaticComplexity(): int
{
return $this->cyclomaticComplexity;
}
}

172
vendor/sebastian/diff/ChangeLog.md vendored Normal file
View File

@ -0,0 +1,172 @@
# ChangeLog
All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [6.0.2] - 2024-07-03
### Changed
* This project now uses PHPStan instead of Psalm for static analysis
## [6.0.1] - 2024-03-02
### Changed
* Do not use implicitly nullable parameters
## [6.0.0] - 2024-02-02
### Removed
* `SebastianBergmann\Diff\Chunk::getStart()`, `SebastianBergmann\Diff\Chunk::getStartRange()`, `SebastianBergmann\Diff\Chunk::getEnd()`, `SebastianBergmann\Diff\Chunk::getEndRange()`, and `SebastianBergmann\Diff\Chunk::getLines()`
* `SebastianBergmann\Diff\Diff::getFrom()`, `SebastianBergmann\Diff\Diff::getTo()`, and `SebastianBergmann\Diff\Diff::getChunks()`
* `SebastianBergmann\Diff\Line::getContent()` and `SebastianBergmann\Diff\Diff::getType()`
* Removed support for PHP 8.1
## [5.1.1] - 2024-03-02
### Changed
* Do not use implicitly nullable parameters
## [5.1.0] - 2023-12-22
### Added
* `SebastianBergmann\Diff\Chunk::start()`, `SebastianBergmann\Diff\Chunk::startRange()`, `SebastianBergmann\Diff\Chunk::end()`, `SebastianBergmann\Diff\Chunk::endRange()`, and `SebastianBergmann\Diff\Chunk::lines()`
* `SebastianBergmann\Diff\Diff::from()`, `SebastianBergmann\Diff\Diff::to()`, and `SebastianBergmann\Diff\Diff::chunks()`
* `SebastianBergmann\Diff\Line::content()` and `SebastianBergmann\Diff\Diff::type()`
* `SebastianBergmann\Diff\Line::isAdded()`,`SebastianBergmann\Diff\Line::isRemoved()`, and `SebastianBergmann\Diff\Line::isUnchanged()`
### Changed
* `SebastianBergmann\Diff\Diff` now implements `IteratorAggregate`, iterating over it yields the aggregated `SebastianBergmann\Diff\Chunk` objects
* `SebastianBergmann\Diff\Chunk` now implements `IteratorAggregate`, iterating over it yields the aggregated `SebastianBergmann\Diff\Line` objects
### Deprecated
* `SebastianBergmann\Diff\Chunk::getStart()`, `SebastianBergmann\Diff\Chunk::getStartRange()`, `SebastianBergmann\Diff\Chunk::getEnd()`, `SebastianBergmann\Diff\Chunk::getEndRange()`, and `SebastianBergmann\Diff\Chunk::getLines()`
* `SebastianBergmann\Diff\Diff::getFrom()`, `SebastianBergmann\Diff\Diff::getTo()`, and `SebastianBergmann\Diff\Diff::getChunks()`
* `SebastianBergmann\Diff\Line::getContent()` and `SebastianBergmann\Diff\Diff::getType()`
## [5.0.3] - 2023-05-01
### Changed
* [#119](https://github.com/sebastianbergmann/diff/pull/119): Improve performance of `TimeEfficientLongestCommonSubsequenceCalculator`
## [5.0.2] - 2023-05-01
### Changed
* [#118](https://github.com/sebastianbergmann/diff/pull/118): Improve performance of `MemoryEfficientLongestCommonSubsequenceCalculator`
## [5.0.1] - 2023-03-23
### Fixed
* [#115](https://github.com/sebastianbergmann/diff/pull/115): `Parser::parseFileDiff()` does not handle diffs correctly that only add lines or only remove lines
## [5.0.0] - 2023-02-03
### Changed
* Passing a `DiffOutputBuilderInterface` instance to `Differ::__construct()` is no longer optional
### Removed
* Removed support for PHP 7.3, PHP 7.4, and PHP 8.0
## [4.0.4] - 2020-10-26
### Fixed
* `SebastianBergmann\Diff\Exception` now correctly extends `\Throwable`
## [4.0.3] - 2020-09-28
### Changed
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [4.0.2] - 2020-06-30
### Added
* This component is now supported on PHP 8
## [4.0.1] - 2020-05-08
### Fixed
* [#99](https://github.com/sebastianbergmann/diff/pull/99): Regression in unified diff output of identical strings
## [4.0.0] - 2020-02-07
### Removed
* Removed support for PHP 7.1 and PHP 7.2
## [3.0.2] - 2019-02-04
### Changed
* `Chunk::setLines()` now ensures that the `$lines` array only contains `Line` objects
## [3.0.1] - 2018-06-10
### Fixed
* Removed `"minimum-stability": "dev",` from `composer.json`
## [3.0.0] - 2018-02-01
* The `StrictUnifiedDiffOutputBuilder` implementation of the `DiffOutputBuilderInterface` was added
### Changed
* The default `DiffOutputBuilderInterface` implementation now generates context lines (unchanged lines)
### Removed
* Removed support for PHP 7.0
### Fixed
* [#70](https://github.com/sebastianbergmann/diff/issues/70): Diffing of arrays no longer works
## [2.0.1] - 2017-08-03
### Fixed
* [#66](https://github.com/sebastianbergmann/diff/pull/66): Restored backwards compatibility for PHPUnit 6.1.4, 6.2.0, 6.2.1, 6.2.2, and 6.2.3
## [2.0.0] - 2017-07-11 [YANKED]
### Added
* [#64](https://github.com/sebastianbergmann/diff/pull/64): Show line numbers for chunks of a diff
### Removed
* This component is no longer supported on PHP 5.6
[6.0.2]: https://github.com/sebastianbergmann/diff/compare/6.0.1...6.0.2
[6.0.1]: https://github.com/sebastianbergmann/diff/compare/6.0.0...6.0.1
[6.0.0]: https://github.com/sebastianbergmann/diff/compare/5.1...6.0.0
[5.1.1]: https://github.com/sebastianbergmann/diff/compare/5.1.0...5.1.1
[5.1.0]: https://github.com/sebastianbergmann/diff/compare/5.0.3...5.1.0
[5.0.3]: https://github.com/sebastianbergmann/diff/compare/5.0.2...5.0.3
[5.0.2]: https://github.com/sebastianbergmann/diff/compare/5.0.1...5.0.2
[5.0.1]: https://github.com/sebastianbergmann/diff/compare/5.0.0...5.0.1
[5.0.0]: https://github.com/sebastianbergmann/diff/compare/4.0.4...5.0.0
[4.0.4]: https://github.com/sebastianbergmann/diff/compare/4.0.3...4.0.4
[4.0.3]: https://github.com/sebastianbergmann/diff/compare/4.0.2...4.0.3
[4.0.2]: https://github.com/sebastianbergmann/diff/compare/4.0.1...4.0.2
[4.0.1]: https://github.com/sebastianbergmann/diff/compare/4.0.0...4.0.1
[4.0.0]: https://github.com/sebastianbergmann/diff/compare/3.0.2...4.0.0
[3.0.2]: https://github.com/sebastianbergmann/diff/compare/3.0.1...3.0.2
[3.0.1]: https://github.com/sebastianbergmann/diff/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/sebastianbergmann/diff/compare/2.0...3.0.0
[2.0.1]: https://github.com/sebastianbergmann/diff/compare/c341c98ce083db77f896a0aa64f5ee7652915970...2.0.1
[2.0.0]: https://github.com/sebastianbergmann/diff/compare/1.4...c341c98ce083db77f896a0aa64f5ee7652915970

29
vendor/sebastian/diff/LICENSE vendored Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2002-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

205
vendor/sebastian/diff/README.md vendored Normal file
View File

@ -0,0 +1,205 @@
[![Latest Stable Version](https://poser.pugx.org/sebastian/diff/v/stable.png)](https://packagist.org/packages/sebastian/diff)
[![CI Status](https://github.com/sebastianbergmann/diff/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/diff/actions)
[![codecov](https://codecov.io/gh/sebastianbergmann/diff/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/diff)
# sebastian/diff
Diff implementation for PHP, factored out of PHPUnit into a stand-alone component.
## Installation
You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/):
```
composer require sebastian/diff
```
If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:
```
composer require --dev sebastian/diff
```
### Usage
#### Generating diff
The `Differ` class can be used to generate a textual representation of the difference between two strings:
```php
<?php
use SebastianBergmann\Diff\Differ;
$differ = new Differ;
print $differ->diff('foo', 'bar');
```
The code above yields the output below:
```diff
--- Original
+++ New
@@ @@
-foo
+bar
```
There are three output builders available in this package:
#### UnifiedDiffOutputBuilder
This is default builder, which generates the output close to udiff and is used by PHPUnit.
```php
<?php
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;
$builder = new UnifiedDiffOutputBuilder(
"--- Original\n+++ New\n", // custom header
false // do not add line numbers to the diff
);
$differ = new Differ($builder);
print $differ->diff('foo', 'bar');
```
#### StrictUnifiedDiffOutputBuilder
Generates (strict) Unified diff's (unidiffs) with hunks,
similar to `diff -u` and compatible with `patch` and `git apply`.
```php
<?php
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder;
$builder = new StrictUnifiedDiffOutputBuilder([
'collapseRanges' => true, // ranges of length one are rendered with the trailing `,1`
'commonLineThreshold' => 6, // number of same lines before ending a new hunk and creating a new one (if needed)
'contextLines' => 3, // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3
'fromFile' => '',
'fromFileDate' => null,
'toFile' => '',
'toFileDate' => null,
]);
$differ = new Differ($builder);
print $differ->diff('foo', 'bar');
```
#### DiffOnlyOutputBuilder
Output only the lines that differ.
```php
<?php
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\DiffOnlyOutputBuilder;
$builder = new DiffOnlyOutputBuilder(
"--- Original\n+++ New\n"
);
$differ = new Differ($builder);
print $differ->diff('foo', 'bar');
```
#### DiffOutputBuilderInterface
You can pass any output builder to the `Differ` class as longs as it implements the `DiffOutputBuilderInterface`.
#### Parsing diff
The `Parser` class can be used to parse a unified diff into an object graph:
```php
use SebastianBergmann\Diff\Parser;
use SebastianBergmann\Git;
$git = new Git('/usr/local/src/money');
$diff = $git->getDiff(
'948a1a07768d8edd10dcefa8315c1cbeffb31833',
'c07a373d2399f3e686234c4f7f088d635eb9641b'
);
$parser = new Parser;
print_r($parser->parse($diff));
```
The code above yields the output below:
Array
(
[0] => SebastianBergmann\Diff\Diff Object
(
[from:SebastianBergmann\Diff\Diff:private] => a/tests/MoneyTest.php
[to:SebastianBergmann\Diff\Diff:private] => b/tests/MoneyTest.php
[chunks:SebastianBergmann\Diff\Diff:private] => Array
(
[0] => SebastianBergmann\Diff\Chunk Object
(
[start:SebastianBergmann\Diff\Chunk:private] => 87
[startRange:SebastianBergmann\Diff\Chunk:private] => 7
[end:SebastianBergmann\Diff\Chunk:private] => 87
[endRange:SebastianBergmann\Diff\Chunk:private] => 7
[lines:SebastianBergmann\Diff\Chunk:private] => Array
(
[0] => SebastianBergmann\Diff\Line Object
(
[type:SebastianBergmann\Diff\Line:private] => 3
[content:SebastianBergmann\Diff\Line:private] => * @covers SebastianBergmann\Money\Money::add
)
[1] => SebastianBergmann\Diff\Line Object
(
[type:SebastianBergmann\Diff\Line:private] => 3
[content:SebastianBergmann\Diff\Line:private] => * @covers SebastianBergmann\Money\Money::newMoney
)
[2] => SebastianBergmann\Diff\Line Object
(
[type:SebastianBergmann\Diff\Line:private] => 3
[content:SebastianBergmann\Diff\Line:private] => */
)
[3] => SebastianBergmann\Diff\Line Object
(
[type:SebastianBergmann\Diff\Line:private] => 2
[content:SebastianBergmann\Diff\Line:private] => public function testAnotherMoneyWithSameCurrencyObjectCanBeAdded()
)
[4] => SebastianBergmann\Diff\Line Object
(
[type:SebastianBergmann\Diff\Line:private] => 1
[content:SebastianBergmann\Diff\Line:private] => public function testAnotherMoneyObjectWithSameCurrencyCanBeAdded()
)
[5] => SebastianBergmann\Diff\Line Object
(
[type:SebastianBergmann\Diff\Line:private] => 3
[content:SebastianBergmann\Diff\Line:private] => {
)
[6] => SebastianBergmann\Diff\Line Object
(
[type:SebastianBergmann\Diff\Line:private] => 3
[content:SebastianBergmann\Diff\Line:private] => $a = new Money(1, new Currency('EUR'));
)
[7] => SebastianBergmann\Diff\Line Object
(
[type:SebastianBergmann\Diff\Line:private] => 3
[content:SebastianBergmann\Diff\Line:private] => $b = new Money(2, new Currency('EUR'));
)
)
)
)
)
)
Note: If the chunk size is 0 lines, i.e., `getStartRange()` or `getEndRange()` return 0, the number of line returned by `getStart()` or `getEnd()` is one lower than one would expect. It is the line number after which the chunk should be inserted or deleted; in all other cases, it gives the first line number of the replaced range of lines.

30
vendor/sebastian/diff/SECURITY.md vendored Normal file
View File

@ -0,0 +1,30 @@
# Security Policy
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email `sebastian@phpunit.de`.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
## Web Context
The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit.
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.

51
vendor/sebastian/diff/composer.json vendored Normal file
View File

@ -0,0 +1,51 @@
{
"name": "sebastian/diff",
"description": "Diff implementation",
"keywords": ["diff", "udiff", "unidiff", "unified diff"],
"homepage": "https://github.com/sebastianbergmann/diff",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Kore Nordmann",
"email": "mail@kore-nordmann.de"
}
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"security": "https://github.com/sebastianbergmann/diff/security/policy"
},
"prefer-stable": true,
"config": {
"platform": {
"php": "8.2.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"require": {
"php": ">=8.2"
},
"require-dev": {
"phpunit/phpunit": "^11.0",
"symfony/process": "^4.2 || ^5"
},
"autoload": {
"classmap": [
"src/"
]
},
"autoload-dev": {
"classmap": [
"tests/"
]
},
"extra": {
"branch-alias": {
"dev-main": "6.0-dev"
}
}
}

89
vendor/sebastian/diff/src/Chunk.php vendored Normal file
View File

@ -0,0 +1,89 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
use ArrayIterator;
use IteratorAggregate;
use Traversable;
/**
* @template-implements IteratorAggregate<int, Line>
*/
final class Chunk implements IteratorAggregate
{
private int $start;
private int $startRange;
private int $end;
private int $endRange;
/**
* @var list<Line>
*/
private array $lines;
/**
* @param list<Line> $lines
*/
public function __construct(int $start = 0, int $startRange = 1, int $end = 0, int $endRange = 1, array $lines = [])
{
$this->start = $start;
$this->startRange = $startRange;
$this->end = $end;
$this->endRange = $endRange;
$this->lines = $lines;
}
public function start(): int
{
return $this->start;
}
public function startRange(): int
{
return $this->startRange;
}
public function end(): int
{
return $this->end;
}
public function endRange(): int
{
return $this->endRange;
}
/**
* @return list<Line>
*/
public function lines(): array
{
return $this->lines;
}
/**
* @param list<Line> $lines
*/
public function setLines(array $lines): void
{
foreach ($lines as $line) {
if (!$line instanceof Line) {
throw new InvalidArgumentException;
}
}
$this->lines = $lines;
}
public function getIterator(): Traversable
{
return new ArrayIterator($this->lines);
}
}

84
vendor/sebastian/diff/src/Diff.php vendored Normal file
View File

@ -0,0 +1,84 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
use ArrayIterator;
use IteratorAggregate;
use Traversable;
/**
* @template-implements IteratorAggregate<int, Chunk>
*/
final class Diff implements IteratorAggregate
{
/**
* @var non-empty-string
*/
private string $from;
/**
* @var non-empty-string
*/
private string $to;
/**
* @var list<Chunk>
*/
private array $chunks;
/**
* @param non-empty-string $from
* @param non-empty-string $to
* @param list<Chunk> $chunks
*/
public function __construct(string $from, string $to, array $chunks = [])
{
$this->from = $from;
$this->to = $to;
$this->chunks = $chunks;
}
/**
* @return non-empty-string
*/
public function from(): string
{
return $this->from;
}
/**
* @return non-empty-string
*/
public function to(): string
{
return $this->to;
}
/**
* @return list<Chunk>
*/
public function chunks(): array
{
return $this->chunks;
}
/**
* @param list<Chunk> $chunks
*/
public function setChunks(array $chunks): void
{
$this->chunks = $chunks;
}
public function getIterator(): Traversable
{
return new ArrayIterator($this->chunks);
}
}

247
vendor/sebastian/diff/src/Differ.php vendored Normal file
View File

@ -0,0 +1,247 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
use const PHP_INT_SIZE;
use const PREG_SPLIT_DELIM_CAPTURE;
use const PREG_SPLIT_NO_EMPTY;
use function array_shift;
use function array_unshift;
use function array_values;
use function count;
use function current;
use function end;
use function is_string;
use function key;
use function min;
use function preg_split;
use function prev;
use function reset;
use function str_ends_with;
use function substr;
use SebastianBergmann\Diff\Output\DiffOutputBuilderInterface;
final class Differ
{
public const OLD = 0;
public const ADDED = 1;
public const REMOVED = 2;
public const DIFF_LINE_END_WARNING = 3;
public const NO_LINE_END_EOF_WARNING = 4;
private DiffOutputBuilderInterface $outputBuilder;
public function __construct(DiffOutputBuilderInterface $outputBuilder)
{
$this->outputBuilder = $outputBuilder;
}
/**
* @param list<string>|string $from
* @param list<string>|string $to
*/
public function diff(array|string $from, array|string $to, ?LongestCommonSubsequenceCalculator $lcs = null): string
{
$diff = $this->diffToArray($from, $to, $lcs);
return $this->outputBuilder->getDiff($diff);
}
/**
* @param list<string>|string $from
* @param list<string>|string $to
*/
public function diffToArray(array|string $from, array|string $to, ?LongestCommonSubsequenceCalculator $lcs = null): array
{
if (is_string($from)) {
$from = $this->splitStringByLines($from);
}
if (is_string($to)) {
$to = $this->splitStringByLines($to);
}
[$from, $to, $start, $end] = self::getArrayDiffParted($from, $to);
if ($lcs === null) {
$lcs = $this->selectLcsImplementation($from, $to);
}
$common = $lcs->calculate(array_values($from), array_values($to));
$diff = [];
foreach ($start as $token) {
$diff[] = [$token, self::OLD];
}
reset($from);
reset($to);
foreach ($common as $token) {
while (($fromToken = reset($from)) !== $token) {
$diff[] = [array_shift($from), self::REMOVED];
}
while (($toToken = reset($to)) !== $token) {
$diff[] = [array_shift($to), self::ADDED];
}
$diff[] = [$token, self::OLD];
array_shift($from);
array_shift($to);
}
while (($token = array_shift($from)) !== null) {
$diff[] = [$token, self::REMOVED];
}
while (($token = array_shift($to)) !== null) {
$diff[] = [$token, self::ADDED];
}
foreach ($end as $token) {
$diff[] = [$token, self::OLD];
}
if ($this->detectUnmatchedLineEndings($diff)) {
array_unshift($diff, ["#Warning: Strings contain different line endings!\n", self::DIFF_LINE_END_WARNING]);
}
return $diff;
}
private function splitStringByLines(string $input): array
{
return preg_split('/(.*\R)/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
}
private function selectLcsImplementation(array $from, array $to): LongestCommonSubsequenceCalculator
{
// We do not want to use the time-efficient implementation if its memory
// footprint will probably exceed this value. Note that the footprint
// calculation is only an estimation for the matrix and the LCS method
// will typically allocate a bit more memory than this.
$memoryLimit = 100 * 1024 * 1024;
if ($this->calculateEstimatedFootprint($from, $to) > $memoryLimit) {
return new MemoryEfficientLongestCommonSubsequenceCalculator;
}
return new TimeEfficientLongestCommonSubsequenceCalculator;
}
private function calculateEstimatedFootprint(array $from, array $to): float|int
{
$itemSize = PHP_INT_SIZE === 4 ? 76 : 144;
return $itemSize * min(count($from), count($to)) ** 2;
}
private function detectUnmatchedLineEndings(array $diff): bool
{
$newLineBreaks = ['' => true];
$oldLineBreaks = ['' => true];
foreach ($diff as $entry) {
if (self::OLD === $entry[1]) {
$ln = $this->getLinebreak($entry[0]);
$oldLineBreaks[$ln] = true;
$newLineBreaks[$ln] = true;
} elseif (self::ADDED === $entry[1]) {
$newLineBreaks[$this->getLinebreak($entry[0])] = true;
} elseif (self::REMOVED === $entry[1]) {
$oldLineBreaks[$this->getLinebreak($entry[0])] = true;
}
}
// if either input or output is a single line without breaks than no warning should be raised
if (['' => true] === $newLineBreaks || ['' => true] === $oldLineBreaks) {
return false;
}
// two-way compare
foreach ($newLineBreaks as $break => $set) {
if (!isset($oldLineBreaks[$break])) {
return true;
}
}
foreach ($oldLineBreaks as $break => $set) {
if (!isset($newLineBreaks[$break])) {
return true;
}
}
return false;
}
private function getLinebreak($line): string
{
if (!is_string($line)) {
return '';
}
$lc = substr($line, -1);
if ("\r" === $lc) {
return "\r";
}
if ("\n" !== $lc) {
return '';
}
if (str_ends_with($line, "\r\n")) {
return "\r\n";
}
return "\n";
}
private static function getArrayDiffParted(array &$from, array &$to): array
{
$start = [];
$end = [];
reset($to);
foreach ($from as $k => $v) {
$toK = key($to);
if ($toK === $k && $v === $to[$k]) {
$start[$k] = $v;
unset($from[$k], $to[$k]);
} else {
break;
}
}
end($from);
end($to);
do {
$fromK = key($from);
$toK = key($to);
if (null === $fromK || null === $toK || current($from) !== current($to)) {
break;
}
prev($from);
prev($to);
$end = [$fromK => $from[$fromK]] + $end;
unset($from[$fromK], $to[$toK]);
} while (true);
return [$from, $to, $start, $end];
}
}

View File

@ -0,0 +1,32 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
use function gettype;
use function is_object;
use function sprintf;
use Exception;
final class ConfigurationException extends InvalidArgumentException
{
public function __construct(string $option, string $expected, mixed $value, int $code = 0, ?Exception $previous = null)
{
parent::__construct(
sprintf(
'Option "%s" must be %s, got "%s".',
$option,
$expected,
is_object($value) ? $value::class : (null === $value ? '<null>' : gettype($value) . '#' . $value),
),
$code,
$previous,
);
}
}

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
use Throwable;
interface Exception extends Throwable
{
}

View File

@ -0,0 +1,14 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
class InvalidArgumentException extends \InvalidArgumentException implements Exception
{
}

50
vendor/sebastian/diff/src/Line.php vendored Normal file
View File

@ -0,0 +1,50 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
final class Line
{
public const ADDED = 1;
public const REMOVED = 2;
public const UNCHANGED = 3;
private int $type;
private string $content;
public function __construct(int $type = self::UNCHANGED, string $content = '')
{
$this->type = $type;
$this->content = $content;
}
public function content(): string
{
return $this->content;
}
public function type(): int
{
return $this->type;
}
public function isAdded(): bool
{
return $this->type === self::ADDED;
}
public function isRemoved(): bool
{
return $this->type === self::REMOVED;
}
public function isUnchanged(): bool
{
return $this->type === self::UNCHANGED;
}
}

View File

@ -0,0 +1,18 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
interface LongestCommonSubsequenceCalculator
{
/**
* Calculates the longest common subsequence of two arrays.
*/
public function calculate(array $from, array $to): array;
}

View File

@ -0,0 +1,97 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
use function array_fill;
use function array_merge;
use function array_reverse;
use function array_slice;
use function count;
use function in_array;
use function max;
final class MemoryEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator
{
/**
* @inheritDoc
*/
public function calculate(array $from, array $to): array
{
$cFrom = count($from);
$cTo = count($to);
if ($cFrom === 0) {
return [];
}
if ($cFrom === 1) {
if (in_array($from[0], $to, true)) {
return [$from[0]];
}
return [];
}
$i = (int) ($cFrom / 2);
$fromStart = array_slice($from, 0, $i);
$fromEnd = array_slice($from, $i);
$llB = $this->length($fromStart, $to);
$llE = $this->length(array_reverse($fromEnd), array_reverse($to));
$jMax = 0;
$max = 0;
for ($j = 0; $j <= $cTo; $j++) {
$m = $llB[$j] + $llE[$cTo - $j];
if ($m >= $max) {
$max = $m;
$jMax = $j;
}
}
$toStart = array_slice($to, 0, $jMax);
$toEnd = array_slice($to, $jMax);
return array_merge(
$this->calculate($fromStart, $toStart),
$this->calculate($fromEnd, $toEnd),
);
}
private function length(array $from, array $to): array
{
$current = array_fill(0, count($to) + 1, 0);
$cFrom = count($from);
$cTo = count($to);
for ($i = 0; $i < $cFrom; $i++) {
$prev = $current;
for ($j = 0; $j < $cTo; $j++) {
if ($from[$i] === $to[$j]) {
$current[$j + 1] = $prev[$j] + 1;
} else {
/**
* @noinspection PhpConditionCanBeReplacedWithMinMaxCallInspection
*
* We do not use max() here to avoid the function call overhead
*/
if ($current[$j] > $prev[$j + 1]) {
$current[$j + 1] = $current[$j];
} else {
$current[$j + 1] = $prev[$j + 1];
}
}
}
}
return $current;
}
}

View File

@ -0,0 +1,52 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff\Output;
use function count;
abstract class AbstractChunkOutputBuilder implements DiffOutputBuilderInterface
{
/**
* Takes input of the diff array and returns the common parts.
* Iterates through diff line by line.
*/
protected function getCommonChunks(array $diff, int $lineThreshold = 5): array
{
$diffSize = count($diff);
$capturing = false;
$chunkStart = 0;
$chunkSize = 0;
$commonChunks = [];
for ($i = 0; $i < $diffSize; $i++) {
if ($diff[$i][1] === 0 /* OLD */) {
if ($capturing === false) {
$capturing = true;
$chunkStart = $i;
$chunkSize = 0;
} else {
$chunkSize++;
}
} elseif ($capturing !== false) {
if ($chunkSize >= $lineThreshold) {
$commonChunks[$chunkStart] = $chunkStart + $chunkSize;
}
$capturing = false;
}
}
if ($capturing !== false && $chunkSize >= $lineThreshold) {
$commonChunks[$chunkStart] = $chunkStart + $chunkSize;
}
return $commonChunks;
}
}

View File

@ -0,0 +1,70 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff\Output;
use function fclose;
use function fopen;
use function fwrite;
use function str_ends_with;
use function stream_get_contents;
use function substr;
use SebastianBergmann\Diff\Differ;
/**
* Builds a diff string representation in a loose unified diff format
* listing only changes lines. Does not include line numbers.
*/
final class DiffOnlyOutputBuilder implements DiffOutputBuilderInterface
{
private string $header;
public function __construct(string $header = "--- Original\n+++ New\n")
{
$this->header = $header;
}
public function getDiff(array $diff): string
{
$buffer = fopen('php://memory', 'r+b');
if ('' !== $this->header) {
fwrite($buffer, $this->header);
if (!str_ends_with($this->header, "\n")) {
fwrite($buffer, "\n");
}
}
foreach ($diff as $diffEntry) {
if ($diffEntry[1] === Differ::ADDED) {
fwrite($buffer, '+' . $diffEntry[0]);
} elseif ($diffEntry[1] === Differ::REMOVED) {
fwrite($buffer, '-' . $diffEntry[0]);
} elseif ($diffEntry[1] === Differ::DIFF_LINE_END_WARNING) {
fwrite($buffer, ' ' . $diffEntry[0]);
continue; // Warnings should not be tested for line break, it will always be there
} else { /* Not changed (old) 0 */
continue; // we didn't write the not-changed line, so do not add a line break either
}
$lc = substr($diffEntry[0], -1);
if ($lc !== "\n" && $lc !== "\r") {
fwrite($buffer, "\n"); // \No newline at end of file
}
}
$diff = stream_get_contents($buffer, -1, 0);
fclose($buffer);
return $diff;
}
}

View File

@ -0,0 +1,19 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff\Output;
/**
* Defines how an output builder should take a generated
* diff array and return a string representation of that diff.
*/
interface DiffOutputBuilderInterface
{
public function getDiff(array $diff): string;
}

View File

@ -0,0 +1,326 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff\Output;
use function array_merge;
use function array_splice;
use function count;
use function fclose;
use function fopen;
use function fwrite;
use function is_bool;
use function is_int;
use function is_string;
use function max;
use function min;
use function sprintf;
use function stream_get_contents;
use function substr;
use SebastianBergmann\Diff\ConfigurationException;
use SebastianBergmann\Diff\Differ;
/**
* Strict Unified diff output builder.
*
* Generates (strict) Unified diff's (unidiffs) with hunks.
*/
final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface
{
private static array $default = [
'collapseRanges' => true, // ranges of length one are rendered with the trailing `,1`
'commonLineThreshold' => 6, // number of same lines before ending a new hunk and creating a new one (if needed)
'contextLines' => 3, // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3
'fromFile' => null,
'fromFileDate' => null,
'toFile' => null,
'toFileDate' => null,
];
private bool $changed;
private bool $collapseRanges;
/**
* @var positive-int
*/
private int $commonLineThreshold;
private string $header;
/**
* @var positive-int
*/
private int $contextLines;
public function __construct(array $options = [])
{
$options = array_merge(self::$default, $options);
if (!is_bool($options['collapseRanges'])) {
throw new ConfigurationException('collapseRanges', 'a bool', $options['collapseRanges']);
}
if (!is_int($options['contextLines']) || $options['contextLines'] < 0) {
throw new ConfigurationException('contextLines', 'an int >= 0', $options['contextLines']);
}
if (!is_int($options['commonLineThreshold']) || $options['commonLineThreshold'] <= 0) {
throw new ConfigurationException('commonLineThreshold', 'an int > 0', $options['commonLineThreshold']);
}
$this->assertString($options, 'fromFile');
$this->assertString($options, 'toFile');
$this->assertStringOrNull($options, 'fromFileDate');
$this->assertStringOrNull($options, 'toFileDate');
$this->header = sprintf(
"--- %s%s\n+++ %s%s\n",
$options['fromFile'],
null === $options['fromFileDate'] ? '' : "\t" . $options['fromFileDate'],
$options['toFile'],
null === $options['toFileDate'] ? '' : "\t" . $options['toFileDate'],
);
$this->collapseRanges = $options['collapseRanges'];
$this->commonLineThreshold = $options['commonLineThreshold'];
$this->contextLines = $options['contextLines'];
}
public function getDiff(array $diff): string
{
if (0 === count($diff)) {
return '';
}
$this->changed = false;
$buffer = fopen('php://memory', 'r+b');
fwrite($buffer, $this->header);
$this->writeDiffHunks($buffer, $diff);
if (!$this->changed) {
fclose($buffer);
return '';
}
$diff = stream_get_contents($buffer, -1, 0);
fclose($buffer);
// If the last char is not a linebreak: add it.
// This might happen when both the `from` and `to` do not have a trailing linebreak
$last = substr($diff, -1);
return "\n" !== $last && "\r" !== $last
? $diff . "\n"
: $diff;
}
private function writeDiffHunks($output, array $diff): void
{
// detect "No newline at end of file" and insert into `$diff` if needed
$upperLimit = count($diff);
if (0 === $diff[$upperLimit - 1][1]) {
$lc = substr($diff[$upperLimit - 1][0], -1);
if ("\n" !== $lc) {
array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]);
}
} else {
// search back for the last `+` and `-` line,
// check if it has a trailing linebreak, else add a warning under it
$toFind = [1 => true, 2 => true];
for ($i = $upperLimit - 1; $i >= 0; $i--) {
if (isset($toFind[$diff[$i][1]])) {
unset($toFind[$diff[$i][1]]);
$lc = substr($diff[$i][0], -1);
if ("\n" !== $lc) {
array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]);
}
if (!count($toFind)) {
break;
}
}
}
}
// write hunks to output buffer
$cutOff = max($this->commonLineThreshold, $this->contextLines);
$hunkCapture = false;
$sameCount = $toRange = $fromRange = 0;
$toStart = $fromStart = 1;
$i = 0;
/** @var int $i */
foreach ($diff as $i => $entry) {
if (0 === $entry[1]) { // same
if (false === $hunkCapture) {
$fromStart++;
$toStart++;
continue;
}
$sameCount++;
$toRange++;
$fromRange++;
if ($sameCount === $cutOff) {
$contextStartOffset = ($hunkCapture - $this->contextLines) < 0
? $hunkCapture
: $this->contextLines;
// note: $contextEndOffset = $this->contextLines;
//
// because we never go beyond the end of the diff.
// with the cutoff/contextlines here the follow is never true;
//
// if ($i - $cutOff + $this->contextLines + 1 > \count($diff)) {
// $contextEndOffset = count($diff) - 1;
// }
//
// ; that would be true for a trailing incomplete hunk case which is dealt with after this loop
$this->writeHunk(
$diff,
$hunkCapture - $contextStartOffset,
$i - $cutOff + $this->contextLines + 1,
$fromStart - $contextStartOffset,
$fromRange - $cutOff + $contextStartOffset + $this->contextLines,
$toStart - $contextStartOffset,
$toRange - $cutOff + $contextStartOffset + $this->contextLines,
$output,
);
$fromStart += $fromRange;
$toStart += $toRange;
$hunkCapture = false;
$sameCount = $toRange = $fromRange = 0;
}
continue;
}
$sameCount = 0;
if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) {
continue;
}
$this->changed = true;
if (false === $hunkCapture) {
$hunkCapture = $i;
}
if (Differ::ADDED === $entry[1]) { // added
$toRange++;
}
if (Differ::REMOVED === $entry[1]) { // removed
$fromRange++;
}
}
if (false === $hunkCapture) {
return;
}
// we end here when cutoff (commonLineThreshold) was not reached, but we were capturing a hunk,
// do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold
$contextStartOffset = $hunkCapture - $this->contextLines < 0
? $hunkCapture
: $this->contextLines;
// prevent trying to write out more common lines than there are in the diff _and_
// do not write more than configured through the context lines
$contextEndOffset = min($sameCount, $this->contextLines);
$fromRange -= $sameCount;
$toRange -= $sameCount;
$this->writeHunk(
$diff,
$hunkCapture - $contextStartOffset,
$i - $sameCount + $contextEndOffset + 1,
$fromStart - $contextStartOffset,
$fromRange + $contextStartOffset + $contextEndOffset,
$toStart - $contextStartOffset,
$toRange + $contextStartOffset + $contextEndOffset,
$output,
);
}
private function writeHunk(
array $diff,
int $diffStartIndex,
int $diffEndIndex,
int $fromStart,
int $fromRange,
int $toStart,
int $toRange,
$output
): void {
fwrite($output, '@@ -' . $fromStart);
if (!$this->collapseRanges || 1 !== $fromRange) {
fwrite($output, ',' . $fromRange);
}
fwrite($output, ' +' . $toStart);
if (!$this->collapseRanges || 1 !== $toRange) {
fwrite($output, ',' . $toRange);
}
fwrite($output, " @@\n");
for ($i = $diffStartIndex; $i < $diffEndIndex; $i++) {
if ($diff[$i][1] === Differ::ADDED) {
$this->changed = true;
fwrite($output, '+' . $diff[$i][0]);
} elseif ($diff[$i][1] === Differ::REMOVED) {
$this->changed = true;
fwrite($output, '-' . $diff[$i][0]);
} elseif ($diff[$i][1] === Differ::OLD) {
fwrite($output, ' ' . $diff[$i][0]);
} elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) {
$this->changed = true;
fwrite($output, $diff[$i][0]);
}
// } elseif ($diff[$i][1] === Differ::DIFF_LINE_END_WARNING) { // custom comment inserted by PHPUnit/diff package
// skip
// } else {
// unknown/invalid
// }
}
}
private function assertString(array $options, string $option): void
{
if (!is_string($options[$option])) {
throw new ConfigurationException($option, 'a string', $options[$option]);
}
}
private function assertStringOrNull(array $options, string $option): void
{
if (null !== $options[$option] && !is_string($options[$option])) {
throw new ConfigurationException($option, 'a string or <null>', $options[$option]);
}
}
}

View File

@ -0,0 +1,257 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff\Output;
use function array_splice;
use function count;
use function fclose;
use function fopen;
use function fwrite;
use function max;
use function min;
use function str_ends_with;
use function stream_get_contents;
use function substr;
use SebastianBergmann\Diff\Differ;
/**
* Builds a diff string representation in unified diff format in chunks.
*/
final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder
{
private bool $collapseRanges = true;
private int $commonLineThreshold = 6;
/**
* @var positive-int
*/
private int $contextLines = 3;
private string $header;
private bool $addLineNumbers;
public function __construct(string $header = "--- Original\n+++ New\n", bool $addLineNumbers = false)
{
$this->header = $header;
$this->addLineNumbers = $addLineNumbers;
}
public function getDiff(array $diff): string
{
$buffer = fopen('php://memory', 'r+b');
if ('' !== $this->header) {
fwrite($buffer, $this->header);
if (!str_ends_with($this->header, "\n")) {
fwrite($buffer, "\n");
}
}
if (0 !== count($diff)) {
$this->writeDiffHunks($buffer, $diff);
}
$diff = stream_get_contents($buffer, -1, 0);
fclose($buffer);
// If the diff is non-empty and last char is not a linebreak: add it.
// This might happen when both the `from` and `to` do not have a trailing linebreak
$last = substr($diff, -1);
return '' !== $diff && "\n" !== $last && "\r" !== $last
? $diff . "\n"
: $diff;
}
private function writeDiffHunks($output, array $diff): void
{
// detect "No newline at end of file" and insert into `$diff` if needed
$upperLimit = count($diff);
if (0 === $diff[$upperLimit - 1][1]) {
$lc = substr($diff[$upperLimit - 1][0], -1);
if ("\n" !== $lc) {
array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]);
}
} else {
// search back for the last `+` and `-` line,
// check if it has trailing linebreak, else add a warning under it
$toFind = [1 => true, 2 => true];
for ($i = $upperLimit - 1; $i >= 0; $i--) {
if (isset($toFind[$diff[$i][1]])) {
unset($toFind[$diff[$i][1]]);
$lc = substr($diff[$i][0], -1);
if ("\n" !== $lc) {
array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]);
}
if (!count($toFind)) {
break;
}
}
}
}
// write hunks to output buffer
$cutOff = max($this->commonLineThreshold, $this->contextLines);
$hunkCapture = false;
$sameCount = $toRange = $fromRange = 0;
$toStart = $fromStart = 1;
$i = 0;
/** @var int $i */
foreach ($diff as $i => $entry) {
if (0 === $entry[1]) { // same
if (false === $hunkCapture) {
$fromStart++;
$toStart++;
continue;
}
$sameCount++;
$toRange++;
$fromRange++;
if ($sameCount === $cutOff) {
$contextStartOffset = ($hunkCapture - $this->contextLines) < 0
? $hunkCapture
: $this->contextLines;
// note: $contextEndOffset = $this->contextLines;
//
// because we never go beyond the end of the diff.
// with the cutoff/contextlines here the follow is never true;
//
// if ($i - $cutOff + $this->contextLines + 1 > \count($diff)) {
// $contextEndOffset = count($diff) - 1;
// }
//
// ; that would be true for a trailing incomplete hunk case which is dealt with after this loop
$this->writeHunk(
$diff,
$hunkCapture - $contextStartOffset,
$i - $cutOff + $this->contextLines + 1,
$fromStart - $contextStartOffset,
$fromRange - $cutOff + $contextStartOffset + $this->contextLines,
$toStart - $contextStartOffset,
$toRange - $cutOff + $contextStartOffset + $this->contextLines,
$output,
);
$fromStart += $fromRange;
$toStart += $toRange;
$hunkCapture = false;
$sameCount = $toRange = $fromRange = 0;
}
continue;
}
$sameCount = 0;
if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) {
continue;
}
if (false === $hunkCapture) {
$hunkCapture = $i;
}
if (Differ::ADDED === $entry[1]) {
$toRange++;
}
if (Differ::REMOVED === $entry[1]) {
$fromRange++;
}
}
if (false === $hunkCapture) {
return;
}
// we end here when cutoff (commonLineThreshold) was not reached, but we were capturing a hunk,
// do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold
$contextStartOffset = $hunkCapture - $this->contextLines < 0
? $hunkCapture
: $this->contextLines;
// prevent trying to write out more common lines than there are in the diff _and_
// do not write more than configured through the context lines
$contextEndOffset = min($sameCount, $this->contextLines);
$fromRange -= $sameCount;
$toRange -= $sameCount;
$this->writeHunk(
$diff,
$hunkCapture - $contextStartOffset,
$i - $sameCount + $contextEndOffset + 1,
$fromStart - $contextStartOffset,
$fromRange + $contextStartOffset + $contextEndOffset,
$toStart - $contextStartOffset,
$toRange + $contextStartOffset + $contextEndOffset,
$output,
);
}
private function writeHunk(
array $diff,
int $diffStartIndex,
int $diffEndIndex,
int $fromStart,
int $fromRange,
int $toStart,
int $toRange,
$output
): void {
if ($this->addLineNumbers) {
fwrite($output, '@@ -' . $fromStart);
if (!$this->collapseRanges || 1 !== $fromRange) {
fwrite($output, ',' . $fromRange);
}
fwrite($output, ' +' . $toStart);
if (!$this->collapseRanges || 1 !== $toRange) {
fwrite($output, ',' . $toRange);
}
fwrite($output, " @@\n");
} else {
fwrite($output, "@@ @@\n");
}
for ($i = $diffStartIndex; $i < $diffEndIndex; $i++) {
if ($diff[$i][1] === Differ::ADDED) {
fwrite($output, '+' . $diff[$i][0]);
} elseif ($diff[$i][1] === Differ::REMOVED) {
fwrite($output, '-' . $diff[$i][0]);
} elseif ($diff[$i][1] === Differ::OLD) {
fwrite($output, ' ' . $diff[$i][0]);
} elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) {
fwrite($output, "\n"); // $diff[$i][0]
} else { /* Not changed (old) Differ::OLD or Warning Differ::DIFF_LINE_END_WARNING */
fwrite($output, ' ' . $diff[$i][0]);
}
}
}
}

112
vendor/sebastian/diff/src/Parser.php vendored Normal file
View File

@ -0,0 +1,112 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
use function array_pop;
use function assert;
use function count;
use function max;
use function preg_match;
use function preg_split;
/**
* Unified diff parser.
*/
final class Parser
{
/**
* @return Diff[]
*/
public function parse(string $string): array
{
$lines = preg_split('(\r\n|\r|\n)', $string);
if (!empty($lines) && $lines[count($lines) - 1] === '') {
array_pop($lines);
}
$lineCount = count($lines);
$diffs = [];
$diff = null;
$collected = [];
for ($i = 0; $i < $lineCount; $i++) {
if (preg_match('#^---\h+"?(?P<file>[^\\v\\t"]+)#', $lines[$i], $fromMatch) &&
preg_match('#^\\+\\+\\+\\h+"?(?P<file>[^\\v\\t"]+)#', $lines[$i + 1], $toMatch)) {
if ($diff !== null) {
$this->parseFileDiff($diff, $collected);
$diffs[] = $diff;
$collected = [];
}
assert(!empty($fromMatch['file']));
assert(!empty($toMatch['file']));
$diff = new Diff($fromMatch['file'], $toMatch['file']);
$i++;
} else {
if (preg_match('/^(?:diff --git |index [\da-f.]+|[+-]{3} [ab])/', $lines[$i])) {
continue;
}
$collected[] = $lines[$i];
}
}
if ($diff !== null && count($collected)) {
$this->parseFileDiff($diff, $collected);
$diffs[] = $diff;
}
return $diffs;
}
private function parseFileDiff(Diff $diff, array $lines): void
{
$chunks = [];
$chunk = null;
$diffLines = [];
foreach ($lines as $line) {
if (preg_match('/^@@\s+-(?P<start>\d+)(?:,\s*(?P<startrange>\d+))?\s+\+(?P<end>\d+)(?:,\s*(?P<endrange>\d+))?\s+@@/', $line, $match, PREG_UNMATCHED_AS_NULL)) {
$chunk = new Chunk(
(int) $match['start'],
isset($match['startrange']) ? max(0, (int) $match['startrange']) : 1,
(int) $match['end'],
isset($match['endrange']) ? max(0, (int) $match['endrange']) : 1,
);
$chunks[] = $chunk;
$diffLines = [];
continue;
}
if (preg_match('/^(?P<type>[+ -])?(?P<line>.*)/', $line, $match)) {
$type = Line::UNCHANGED;
if ($match['type'] === '+') {
$type = Line::ADDED;
} elseif ($match['type'] === '-') {
$type = Line::REMOVED;
}
$diffLines[] = new Line($type, $match['line']);
$chunk?->setLines($diffLines);
}
}
$diff->setChunks($chunks);
}
}

View File

@ -0,0 +1,82 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/diff.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Diff;
use function array_reverse;
use function count;
use function max;
use SplFixedArray;
final class TimeEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator
{
/**
* @inheritDoc
*/
public function calculate(array $from, array $to): array
{
$common = [];
$fromLength = count($from);
$toLength = count($to);
$width = $fromLength + 1;
$matrix = new SplFixedArray($width * ($toLength + 1));
for ($i = 0; $i <= $fromLength; $i++) {
$matrix[$i] = 0;
}
for ($j = 0; $j <= $toLength; $j++) {
$matrix[$j * $width] = 0;
}
for ($i = 1; $i <= $fromLength; $i++) {
for ($j = 1; $j <= $toLength; $j++) {
$o = ($j * $width) + $i;
// don't use max() to avoid function call overhead
$firstOrLast = $from[$i - 1] === $to[$j - 1] ? $matrix[$o - $width - 1] + 1 : 0;
if ($matrix[$o - 1] > $matrix[$o - $width]) {
if ($firstOrLast > $matrix[$o - 1]) {
$matrix[$o] = $firstOrLast;
} else {
$matrix[$o] = $matrix[$o - 1];
}
} else {
if ($firstOrLast > $matrix[$o - $width]) {
$matrix[$o] = $firstOrLast;
} else {
$matrix[$o] = $matrix[$o - $width];
}
}
}
}
$i = $fromLength;
$j = $toLength;
while ($i > 0 && $j > 0) {
if ($from[$i - 1] === $to[$j - 1]) {
$common[] = $from[$i - 1];
$i--;
$j--;
} else {
$o = ($j * $width) + $i;
if ($matrix[$o - $width] > $matrix[$o - 1]) {
$j--;
} else {
$i--;
}
}
}
return array_reverse($common);
}
}

View File

@ -0,0 +1,242 @@
# Changes in sebastianbergmann/environment
All notable changes in `sebastianbergmann/environment` are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [7.2.0] - 2024-07-03
### Changed
* Synced `Console::hasColorSupport()` with Symfony's `StreamOutput::hasColorSupport()` implementation
* Removed code left over from a time before PHP 5.4 and when HHVM was still supported
* This project now uses PHPStan instead of Psalm for static analysis
### Deprecated
* The `Runtime::getBinary()` method is now deprecated, use `escapeshellarg(PHP_BINARY)` instead
* The `Runtime::getRawBinary()` method is now deprecated, use the `PHP_BINARY` constant instead
## [7.1.0] - 2024-03-23
### Added
* [#72](https://github.com/sebastianbergmann/environment/pull/72): `Runtime::getRawBinary()`
## [7.0.0] - 2024-02-02
### Removed
* This component is no longer supported on PHP 8.1
## [6.1.1] - 2024-MM-DD
### Changed
* Synced `Console::hasColorSupport()` with Symfony's `StreamOutput::hasColorSupport()` implementation
## [6.1.0] - 2024-03-23
### Added
* [#72](https://github.com/sebastianbergmann/environment/pull/72): `Runtime::getRawBinary()`
## [6.0.1] - 2023-04-11
### Fixed
* [#68](https://github.com/sebastianbergmann/environment/pull/68): The Just-in-Time compiler is disabled when `opcache.jit_buffer_size` is set to `0`
* [#70](https://github.com/sebastianbergmann/environment/pull/70): The first `0` of `opcache.jit` only disables CPU-specific optimizations, not the Just-in-Time compiler itself
## [6.0.0] - 2023-02-03
### Removed
* Removed `SebastianBergmann\Environment\OperatingSystem::getFamily()` because this component is no longer supported on PHP versions that do not have `PHP_OS_FAMILY`
* Removed `SebastianBergmann\Environment\Runtime::isHHVM()`
* This component is no longer supported on PHP 7.3, PHP 7.4, and PHP 8.0
## [5.1.5] - 2022-MM-DD
### Fixed
* [#59](https://github.com/sebastianbergmann/environment/issues/59): Wrong usage of `stream_isatty()`, `fstat()` used without checking whether the function is available
## [5.1.4] - 2022-04-03
### Fixed
* [#63](https://github.com/sebastianbergmann/environment/pull/63): `Runtime::getCurrentSettings()` does not correctly process INI settings
## [5.1.3] - 2020-09-28
### Changed
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [5.1.2] - 2020-06-26
### Added
* This component is now supported on PHP 8
## [5.1.1] - 2020-06-15
### Changed
* Tests etc. are now ignored for archive exports
## [5.1.0] - 2020-04-14
### Added
* `Runtime::performsJustInTimeCompilation()` returns `true` if PHP 8's JIT is active, `false` otherwise
## [5.0.2] - 2020-03-31
### Fixed
* [#55](https://github.com/sebastianbergmann/environment/issues/55): `stty` command is executed even if no tty is available
## [5.0.1] - 2020-02-19
### Changed
* `Runtime::getNameWithVersionAndCodeCoverageDriver()` now prioritizes PCOV over Xdebug when both extensions are loaded (just like php-code-coverage does)
## [5.0.0] - 2020-02-07
### Removed
* This component is no longer supported on PHP 7.1 and PHP 7.2
## [4.2.3] - 2019-11-20
### Changed
* [#50](https://github.com/sebastianbergmann/environment/pull/50): Windows improvements to console capabilities
### Fixed
* [#49](https://github.com/sebastianbergmann/environment/issues/49): Detection how OpCache handles docblocks does not work correctly when PHPDBG is used
## [4.2.2] - 2019-05-05
### Fixed
* [#44](https://github.com/sebastianbergmann/environment/pull/44): `TypeError` in `Console::getNumberOfColumnsInteractive()`
## [4.2.1] - 2019-04-25
### Fixed
* Fixed an issue in `Runtime::getCurrentSettings()`
## [4.2.0] - 2019-04-25
### Added
* [#36](https://github.com/sebastianbergmann/environment/pull/36): `Runtime::getCurrentSettings()`
## [4.1.0] - 2019-02-01
### Added
* Implemented `Runtime::getNameWithVersionAndCodeCoverageDriver()` method
* [#34](https://github.com/sebastianbergmann/environment/pull/34): Support for PCOV extension
## [4.0.2] - 2019-01-28
### Fixed
* [#33](https://github.com/sebastianbergmann/environment/issues/33): `Runtime::discardsComments()` returns true too eagerly
### Removed
* Removed support for Zend Optimizer+ in `Runtime::discardsComments()`
## [4.0.1] - 2018-11-25
### Fixed
* [#31](https://github.com/sebastianbergmann/environment/issues/31): Regressions in `Console` class
## [4.0.0] - 2018-10-23 [YANKED]
### Fixed
* [#25](https://github.com/sebastianbergmann/environment/pull/25): `Console::hasColorSupport()` does not work on Windows
### Removed
* This component is no longer supported on PHP 7.0
## [3.1.0] - 2017-07-01
### Added
* [#21](https://github.com/sebastianbergmann/environment/issues/21): Equivalent of `PHP_OS_FAMILY` (for PHP < 7.2)
## [3.0.4] - 2017-06-20
### Fixed
* [#20](https://github.com/sebastianbergmann/environment/pull/20): PHP 7 mode of HHVM not forced
## [3.0.3] - 2017-05-18
### Fixed
* [#18](https://github.com/sebastianbergmann/environment/issues/18): `Uncaught TypeError: preg_match() expects parameter 2 to be string, null given`
## [3.0.2] - 2017-04-21
### Fixed
* [#17](https://github.com/sebastianbergmann/environment/issues/17): `Uncaught TypeError: trim() expects parameter 1 to be string, boolean given`
## [3.0.1] - 2017-04-21
### Fixed
* Fixed inverted logic in `Runtime::discardsComments()`
## [3.0.0] - 2017-04-21
### Added
* Implemented `Runtime::discardsComments()` for querying whether the PHP runtime discards annotations
### Removed
* This component is no longer supported on PHP 5.6
[7.2.0]: https://github.com/sebastianbergmann/environment/compare/7.1.0...7.2.0
[7.1.0]: https://github.com/sebastianbergmann/environment/compare/7.0.0...7.1.0
[7.0.0]: https://github.com/sebastianbergmann/environment/compare/6.1...7.0.0
[6.1.1]: https://github.com/sebastianbergmann/environment/compare/6.1.0...6.1
[6.1.0]: https://github.com/sebastianbergmann/environment/compare/6.0.1...6.1.0
[6.0.1]: https://github.com/sebastianbergmann/environment/compare/6.0.0...6.0.1
[6.0.0]: https://github.com/sebastianbergmann/environment/compare/5.1.5...6.0.0
[5.1.5]: https://github.com/sebastianbergmann/environment/compare/5.1.4...5.1.5
[5.1.4]: https://github.com/sebastianbergmann/environment/compare/5.1.3...5.1.4
[5.1.3]: https://github.com/sebastianbergmann/environment/compare/5.1.2...5.1.3
[5.1.2]: https://github.com/sebastianbergmann/environment/compare/5.1.1...5.1.2
[5.1.1]: https://github.com/sebastianbergmann/environment/compare/5.1.0...5.1.1
[5.1.0]: https://github.com/sebastianbergmann/environment/compare/5.0.2...5.1.0
[5.0.2]: https://github.com/sebastianbergmann/environment/compare/5.0.1...5.0.2
[5.0.1]: https://github.com/sebastianbergmann/environment/compare/5.0.0...5.0.1
[5.0.0]: https://github.com/sebastianbergmann/environment/compare/4.2.3...5.0.0
[4.2.3]: https://github.com/sebastianbergmann/environment/compare/4.2.2...4.2.3
[4.2.2]: https://github.com/sebastianbergmann/environment/compare/4.2.1...4.2.2
[4.2.1]: https://github.com/sebastianbergmann/environment/compare/4.2.0...4.2.1
[4.2.0]: https://github.com/sebastianbergmann/environment/compare/4.1.0...4.2.0
[4.1.0]: https://github.com/sebastianbergmann/environment/compare/4.0.2...4.1.0
[4.0.2]: https://github.com/sebastianbergmann/environment/compare/4.0.1...4.0.2
[4.0.1]: https://github.com/sebastianbergmann/environment/compare/66691f8e2dc4641909166b275a9a4f45c0e89092...4.0.1
[4.0.0]: https://github.com/sebastianbergmann/environment/compare/3.1.0...66691f8e2dc4641909166b275a9a4f45c0e89092
[3.1.0]: https://github.com/sebastianbergmann/environment/compare/3.0...3.1.0
[3.0.4]: https://github.com/sebastianbergmann/environment/compare/3.0.3...3.0.4
[3.0.3]: https://github.com/sebastianbergmann/environment/compare/3.0.2...3.0.3
[3.0.2]: https://github.com/sebastianbergmann/environment/compare/3.0.1...3.0.2
[3.0.1]: https://github.com/sebastianbergmann/environment/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/sebastianbergmann/environment/compare/2.0...3.0.0

29
vendor/sebastian/environment/LICENSE vendored Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2014-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21
vendor/sebastian/environment/README.md vendored Normal file
View File

@ -0,0 +1,21 @@
[![Latest Stable Version](https://poser.pugx.org/sebastian/environment/v/stable.png)](https://packagist.org/packages/sebastian/environment)
[![CI Status](https://github.com/sebastianbergmann/environment/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/environment/actions)
[![codecov](https://codecov.io/gh/sebastianbergmann/environment/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/environment)
# sebastian/environment
This component provides functionality that helps writing PHP code that has runtime-specific (PHP / HHVM) execution paths.
## Installation
You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/):
```
composer require sebastian/environment
```
If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:
```
composer require --dev sebastian/environment
```

View File

@ -0,0 +1,30 @@
# Security Policy
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email `sebastian@phpunit.de`.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
## Web Context
The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit.
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.

View File

@ -0,0 +1,44 @@
{
"name": "sebastian/environment",
"description": "Provides functionality to handle HHVM/PHP environments",
"keywords": ["environment","hhvm","xdebug"],
"homepage": "https://github.com/sebastianbergmann/environment",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"security": "https://github.com/sebastianbergmann/environment/security/policy"
},
"config": {
"platform": {
"php": "8.2.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"prefer-stable": true,
"require": {
"php": ">=8.2"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
},
"suggest": {
"ext-posix": "*"
},
"autoload": {
"classmap": [
"src/"
]
},
"extra": {
"branch-alias": {
"dev-main": "7.2-dev"
}
}
}

View File

@ -0,0 +1,200 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/environment.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Environment;
use const DIRECTORY_SEPARATOR;
use const STDIN;
use const STDOUT;
use function defined;
use function fclose;
use function fstat;
use function function_exists;
use function getenv;
use function in_array;
use function is_resource;
use function is_string;
use function posix_isatty;
use function preg_match;
use function proc_close;
use function proc_open;
use function sapi_windows_vt100_support;
use function shell_exec;
use function stream_get_contents;
use function stream_isatty;
use function strtoupper;
use function trim;
final class Console
{
/**
* @var int
*/
public const STDIN = 0;
/**
* @var int
*/
public const STDOUT = 1;
/**
* @var int
*/
public const STDERR = 2;
/**
* Returns true if STDOUT supports colorization.
*
* This code has been copied and adapted from
* Symfony\Component\Console\Output\StreamOutput.
*/
public function hasColorSupport(): bool
{
if (!defined('STDOUT')) {
return false;
}
if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) {
return false;
}
if (!@stream_isatty(STDOUT) &&
!in_array(strtoupper((string) getenv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) {
return false;
}
if ($this->isWindows() &&
function_exists('sapi_windows_vt100_support') &&
@sapi_windows_vt100_support(STDOUT)) {
return true;
}
if ('Hyper' === getenv('TERM_PROGRAM') ||
false !== getenv('COLORTERM') ||
false !== getenv('ANSICON') ||
'ON' === getenv('ConEmuANSI')) {
return true;
}
if ('dumb' === $term = (string) getenv('TERM')) {
return false;
}
return (bool) preg_match('/^((screen|xterm|vt100|vt220|putty|rxvt|ansi|cygwin|linux).*)|(.*-256(color)?(-bce)?)$/', $term);
}
/**
* Returns the number of columns of the terminal.
*
* @codeCoverageIgnore
*/
public function getNumberOfColumns(): int
{
if (!$this->isInteractive(defined('STDIN') ? STDIN : self::STDIN)) {
return 80;
}
if ($this->isWindows()) {
return $this->getNumberOfColumnsWindows();
}
return $this->getNumberOfColumnsInteractive();
}
/**
* Returns if the file descriptor is an interactive terminal or not.
*
* Normally, we want to use a resource as a parameter, yet sadly it's not always available,
* eg when running code in interactive console (`php -a`), STDIN/STDOUT/STDERR constants are not defined.
*
* @param int|resource $fileDescriptor
*/
public function isInteractive($fileDescriptor = self::STDOUT): bool
{
if (is_resource($fileDescriptor)) {
if (function_exists('stream_isatty') && @stream_isatty($fileDescriptor)) {
return true;
}
if (function_exists('fstat')) {
$stat = @fstat(STDOUT);
return $stat && 0o020000 === ($stat['mode'] & 0o170000);
}
return false;
}
return function_exists('posix_isatty') && @posix_isatty($fileDescriptor);
}
private function isWindows(): bool
{
return DIRECTORY_SEPARATOR === '\\';
}
/**
* @codeCoverageIgnore
*/
private function getNumberOfColumnsInteractive(): int
{
if (function_exists('shell_exec') && preg_match('#\d+ (\d+)#', shell_exec('stty size') ?: '', $match) === 1) {
if ((int) $match[1] > 0) {
return (int) $match[1];
}
}
if (function_exists('shell_exec') && preg_match('#columns = (\d+);#', shell_exec('stty') ?: '', $match) === 1) {
if ((int) $match[1] > 0) {
return (int) $match[1];
}
}
return 80;
}
/**
* @codeCoverageIgnore
*/
private function getNumberOfColumnsWindows(): int
{
$ansicon = getenv('ANSICON');
$columns = 80;
if (is_string($ansicon) && preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim($ansicon), $matches)) {
$columns = (int) $matches[1];
} elseif (function_exists('proc_open')) {
$process = proc_open(
'mode CON',
[
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
],
$pipes,
null,
null,
['suppress_errors' => true],
);
if (is_resource($process)) {
$info = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', (string) $info, $matches)) {
$columns = (int) $matches[2];
}
}
}
return $columns - 1;
}
}

View File

@ -0,0 +1,264 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/environment.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Environment;
use const PHP_BINARY;
use const PHP_MAJOR_VERSION;
use const PHP_SAPI;
use const PHP_VERSION;
use function array_map;
use function array_merge;
use function escapeshellarg;
use function explode;
use function extension_loaded;
use function ini_get;
use function parse_ini_file;
use function php_ini_loaded_file;
use function php_ini_scanned_files;
use function phpversion;
use function sprintf;
use function strrpos;
final class Runtime
{
/**
* Returns true when Xdebug or PCOV is available or
* the runtime used is PHPDBG.
*/
public function canCollectCodeCoverage(): bool
{
return $this->hasXdebug() || $this->hasPCOV() || $this->hasPHPDBGCodeCoverage();
}
/**
* Returns true when Zend OPcache is loaded, enabled,
* and is configured to discard comments.
*/
public function discardsComments(): bool
{
if (!$this->isOpcacheActive()) {
return false;
}
if (ini_get('opcache.save_comments') !== '0') {
return false;
}
return true;
}
/**
* Returns true when Zend OPcache is loaded, enabled,
* and is configured to perform just-in-time compilation.
*/
public function performsJustInTimeCompilation(): bool
{
if (PHP_MAJOR_VERSION < 8) {
return false;
}
if (!$this->isOpcacheActive()) {
return false;
}
if (ini_get('opcache.jit_buffer_size') === '0') {
return false;
}
$jit = (string) ini_get('opcache.jit');
if (($jit === 'disable') || ($jit === 'off')) {
return false;
}
if (strrpos($jit, '0') === 3) {
return false;
}
return true;
}
/**
* Returns the raw path to the binary of the current runtime.
*
* @deprecated
*/
public function getRawBinary(): string
{
return PHP_BINARY;
}
/**
* Returns the escaped path to the binary of the current runtime.
*
* @deprecated
*/
public function getBinary(): string
{
return escapeshellarg(PHP_BINARY);
}
public function getNameWithVersion(): string
{
return $this->getName() . ' ' . $this->getVersion();
}
public function getNameWithVersionAndCodeCoverageDriver(): string
{
if ($this->hasPCOV()) {
return sprintf(
'%s with PCOV %s',
$this->getNameWithVersion(),
phpversion('pcov'),
);
}
if ($this->hasXdebug()) {
return sprintf(
'%s with Xdebug %s',
$this->getNameWithVersion(),
phpversion('xdebug'),
);
}
return $this->getNameWithVersion();
}
public function getName(): string
{
if ($this->isPHPDBG()) {
// @codeCoverageIgnoreStart
return 'PHPDBG';
// @codeCoverageIgnoreEnd
}
return 'PHP';
}
public function getVendorUrl(): string
{
return 'https://www.php.net/';
}
public function getVersion(): string
{
return PHP_VERSION;
}
/**
* Returns true when the runtime used is PHP and Xdebug is loaded.
*/
public function hasXdebug(): bool
{
return $this->isPHP() && extension_loaded('xdebug');
}
/**
* Returns true when the runtime used is PHP without the PHPDBG SAPI.
*/
public function isPHP(): bool
{
return !$this->isPHPDBG();
}
/**
* Returns true when the runtime used is PHP with the PHPDBG SAPI.
*/
public function isPHPDBG(): bool
{
return PHP_SAPI === 'phpdbg';
}
/**
* Returns true when the runtime used is PHP with the PHPDBG SAPI
* and the phpdbg_*_oplog() functions are available (PHP >= 7.0).
*/
public function hasPHPDBGCodeCoverage(): bool
{
return $this->isPHPDBG();
}
/**
* Returns true when the runtime used is PHP with PCOV loaded and enabled.
*/
public function hasPCOV(): bool
{
return $this->isPHP() && extension_loaded('pcov') && ini_get('pcov.enabled');
}
/**
* Parses the loaded php.ini file (if any) as well as all
* additional php.ini files from the additional ini dir for
* a list of all configuration settings loaded from files
* at startup. Then checks for each php.ini setting passed
* via the `$values` parameter whether this setting has
* been changed at runtime. Returns an array of strings
* where each string has the format `key=value` denoting
* the name of a changed php.ini setting with its new value.
*
* @param list<string> $values
*
* @return array<string, string>
*/
public function getCurrentSettings(array $values): array
{
$diff = [];
$files = [];
if ($file = php_ini_loaded_file()) {
$files[] = $file;
}
if ($scanned = php_ini_scanned_files()) {
$files = array_merge(
$files,
array_map(
'trim',
explode(",\n", $scanned),
),
);
}
foreach ($files as $ini) {
$config = parse_ini_file($ini, true);
foreach ($values as $value) {
$set = ini_get($value);
if (empty($set)) {
continue;
}
if ((!isset($config[$value]) || ($set !== $config[$value]))) {
$diff[$value] = sprintf('%s=%s', $value, $set);
}
}
}
return $diff;
}
private function isOpcacheActive(): bool
{
if (!extension_loaded('Zend OPcache')) {
return false;
}
if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && ini_get('opcache.enable_cli') === '1') {
return true;
}
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' && ini_get('opcache.enable') === '1') {
return true;
}
return false;
}
}

183
vendor/sebastian/exporter/ChangeLog.md vendored Normal file
View File

@ -0,0 +1,183 @@
# ChangeLog
All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
## [6.1.3] - 2024-07-03
### Changed
* [#66](https://github.com/sebastianbergmann/exporter/pull/66): Avoid using the Reflection API for some classes
* This project now uses PHPStan instead of Psalm for static analysis
## [6.1.2] - 2024-06-18
### Changed
* [#64](https://github.com/sebastianbergmann/exporter/pull/64): Improve performance of `Exporter::exportString()`
* [#65](https://github.com/sebastianbergmann/exporter/pull/65): Prevent unnecessary calls to `str_repeat()`
### Fixed
* [#62](https://github.com/sebastianbergmann/exporter/issues/62): Do not limit export of arrays by default (to restore BC with versions prior to 6.1.0)
## [6.1.1] - 2024-06-18
### Fixed
* [#61](https://github.com/sebastianbergmann/exporter/issues/61): `count(): Recursion detected` warning triggered
## [6.1.0] - 2024-06-18
### Added
* [#59](https://github.com/sebastianbergmann/exporter/pull/59): Option to limit export of (large) arrays (to speed up PHPUnit)
### Changed
* [#60](https://github.com/sebastianbergmann/exporter/pull/60): Take shortcut when exporting a string
## [6.0.3] - 2024-06-17
### Fixed
* Fixed code coverage metadata
## [6.0.2] - 2024-06-17 [YANKED]
### Changed
* [#58](https://github.com/sebastianbergmann/exporter/pull/58): Remove unnecessary `sprintf()` in hot path
## [6.0.1] - 2024-03-02
### Changed
* Do not use implicitly nullable parameters
## [6.0.0] - 2024-02-02
### Removed
* This component is no longer supported on PHP 8.1
## [5.1.2] - 2024-03-02
### Changed
* Do not use implicitly nullable parameters
## [5.1.1] - 2023-09-24
### Changed
* [#52](https://github.com/sebastianbergmann/exporter/pull/52): Optimize export of large arrays and object graphs
## [5.1.0] - 2023-09-18
### Changed
* [#51](https://github.com/sebastianbergmann/exporter/pull/51): Export arrays using short array syntax
## [5.0.1] - 2023-09-08
### Fixed
* [#49](https://github.com/sebastianbergmann/exporter/issues/49): `Exporter::toArray()` changes `SplObjectStorage` index
## [5.0.0] - 2023-02-03
### Changed
* [#42](https://github.com/sebastianbergmann/exporter/pull/42): Improve export of enumerations
### Removed
* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0
## [4.0.5] - 2022-09-14
### Fixed
* [#47](https://github.com/sebastianbergmann/exporter/pull/47): Fix `float` export precision
## [4.0.4] - 2021-11-11
### Changed
* [#37](https://github.com/sebastianbergmann/exporter/pull/37): Improve export of closed resources
## [4.0.3] - 2020-09-28
### Changed
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [4.0.2] - 2020-06-26
### Added
* This component is now supported on PHP 8
## [4.0.1] - 2020-06-15
### Changed
* Tests etc. are now ignored for archive exports
## [4.0.0] - 2020-02-07
### Removed
* This component is no longer supported on PHP 7.0, PHP 7.1, and PHP 7.2
## [3.1.5] - 2022-09-14
### Fixed
* [#47](https://github.com/sebastianbergmann/exporter/pull/47): Fix `float` export precision
## [3.1.4] - 2021-11-11
### Changed
* [#38](https://github.com/sebastianbergmann/exporter/pull/38): Improve export of closed resources
## [3.1.3] - 2020-11-30
### Changed
* Changed PHP version constraint in `composer.json` from `^7.0` to `>=7.0`
## [3.1.2] - 2019-09-14
### Fixed
* [#29](https://github.com/sebastianbergmann/exporter/pull/29): Second parameter for `str_repeat()` must be an integer
### Removed
* Remove HHVM-specific code that is no longer needed
[6.1.3]: https://github.com/sebastianbergmann/exporter/compare/6.1.2...6.1.3
[6.1.2]: https://github.com/sebastianbergmann/exporter/compare/6.1.1...6.1.2
[6.1.1]: https://github.com/sebastianbergmann/exporter/compare/6.1.0...6.1.1
[6.1.0]: https://github.com/sebastianbergmann/exporter/compare/6.0.3...6.1.0
[6.0.3]: https://github.com/sebastianbergmann/exporter/compare/fe0dca49a60d34440e2f086951952dd13aa9a5d2...6.0.3
[6.0.2]: https://github.com/sebastianbergmann/exporter/compare/6.0.1...fe0dca49a60d34440e2f086951952dd13aa9a5d2
[6.0.1]: https://github.com/sebastianbergmann/exporter/compare/6.0.0...6.0.1
[6.0.0]: https://github.com/sebastianbergmann/exporter/compare/5.1...6.0.0
[5.1.2]: https://github.com/sebastianbergmann/exporter/compare/5.1.1...5.1.2
[5.1.1]: https://github.com/sebastianbergmann/exporter/compare/5.1.0...5.1.1
[5.1.0]: https://github.com/sebastianbergmann/exporter/compare/5.0.1...5.1.0
[5.0.1]: https://github.com/sebastianbergmann/exporter/compare/5.0.0...5.0.1
[5.0.0]: https://github.com/sebastianbergmann/exporter/compare/4.0.5...5.0.0
[4.0.5]: https://github.com/sebastianbergmann/exporter/compare/4.0.4...4.0.5
[4.0.4]: https://github.com/sebastianbergmann/exporter/compare/4.0.3...4.0.4
[4.0.3]: https://github.com/sebastianbergmann/exporter/compare/4.0.2...4.0.3
[4.0.2]: https://github.com/sebastianbergmann/exporter/compare/4.0.1...4.0.2
[4.0.1]: https://github.com/sebastianbergmann/exporter/compare/4.0.0...4.0.1
[4.0.0]: https://github.com/sebastianbergmann/exporter/compare/3.1.2...4.0.0
[3.1.5]: https://github.com/sebastianbergmann/exporter/compare/3.1.4...3.1.5
[3.1.4]: https://github.com/sebastianbergmann/exporter/compare/3.1.3...3.1.4
[3.1.3]: https://github.com/sebastianbergmann/exporter/compare/3.1.2...3.1.3
[3.1.2]: https://github.com/sebastianbergmann/exporter/compare/3.1.1...3.1.2

29
vendor/sebastian/exporter/LICENSE vendored Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2002-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

175
vendor/sebastian/exporter/README.md vendored Normal file
View File

@ -0,0 +1,175 @@
[![Latest Stable Version](https://poser.pugx.org/sebastian/exporter/v/stable.png)](https://packagist.org/packages/sebastian/exporter)
[![CI Status](https://github.com/sebastianbergmann/exporter/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/exporter/actions)
[![codecov](https://codecov.io/gh/sebastianbergmann/exporter/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/exporter)
# sebastian/exporter
This component provides the functionality to export PHP variables for visualization.
## Installation
You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/):
```
composer require sebastian/exporter
```
If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:
```
composer require --dev sebastian/exporter
```
## Usage
Exporting:
```php
<?php
use SebastianBergmann\Exporter\Exporter;
$exporter = new Exporter;
/*
Exception Object &0000000078de0f0d000000002003a261 (
'message' => ''
'string' => ''
'code' => 0
'file' => '/home/sebastianbergmann/test.php'
'line' => 34
'previous' => null
)
*/
print $exporter->export(new Exception);
```
## Data Types
Exporting simple types:
```php
<?php
use SebastianBergmann\Exporter\Exporter;
$exporter = new Exporter;
// 46
print $exporter->export(46);
// 4.0
print $exporter->export(4.0);
// 'hello, world!'
print $exporter->export('hello, world!');
// false
print $exporter->export(false);
// NAN
print $exporter->export(acos(8));
// -INF
print $exporter->export(log(0));
// null
print $exporter->export(null);
// resource(13) of type (stream)
print $exporter->export(fopen('php://stderr', 'w'));
// Binary String: 0x000102030405
print $exporter->export(chr(0) . chr(1) . chr(2) . chr(3) . chr(4) . chr(5));
```
Exporting complex types:
```php
<?php
use SebastianBergmann\Exporter\Exporter;
$exporter = new Exporter;
/*
Array &0 (
0 => Array &1 (
0 => 1
1 => 2
2 => 3
)
1 => Array &2 (
0 => ''
1 => 0
2 => false
)
)
*/
print $exporter->export(array(array(1,2,3), array("",0,FALSE)));
/*
Array &0 (
'self' => Array &1 (
'self' => Array &1
)
)
*/
$array = array();
$array['self'] = &$array;
print $exporter->export($array);
/*
stdClass Object &0000000003a66dcc0000000025e723e2 (
'self' => stdClass Object &0000000003a66dcc0000000025e723e2
)
*/
$obj = new stdClass();
$obj->self = $obj;
print $exporter->export($obj);
```
Compact exports:
```php
<?php
use SebastianBergmann\Exporter\Exporter;
$exporter = new Exporter;
// Array ()
print $exporter->shortenedExport(array());
// Array (...)
print $exporter->shortenedExport(array(1,2,3,4,5));
// stdClass Object ()
print $exporter->shortenedExport(new stdClass);
// Exception Object (...)
print $exporter->shortenedExport(new Exception);
// this\nis\na\nsuper\nlong\nstring\nt...\nspace
print $exporter->shortenedExport(
<<<LONG_STRING
this
is
a
super
long
string
that
wraps
a
lot
and
eats
up
a
lot
of
space
LONG_STRING
);
```

30
vendor/sebastian/exporter/SECURITY.md vendored Normal file
View File

@ -0,0 +1,30 @@
# Security Policy
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email `sebastian@phpunit.de`.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
## Web Context
The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit.
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.

65
vendor/sebastian/exporter/composer.json vendored Normal file
View File

@ -0,0 +1,65 @@
{
"name": "sebastian/exporter",
"description": "Provides the functionality to export PHP variables for visualization",
"keywords": ["exporter","export"],
"homepage": "https://www.github.com/sebastianbergmann/exporter",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
"security": "https://github.com/sebastianbergmann/exporter/security/policy"
},
"config": {
"platform": {
"php": "8.2.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"prefer-stable": true,
"require": {
"php": ">=8.2",
"ext-mbstring": "*",
"sebastian/recursion-context": "^6.0"
},
"require-dev": {
"phpunit/phpunit": "^11.2"
},
"autoload": {
"classmap": [
"src/"
]
},
"autoload-dev": {
"classmap": [
"tests/_fixture"
]
},
"extra": {
"branch-alias": {
"dev-main": "6.1-dev"
}
}
}

View File

@ -0,0 +1,432 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/exporter.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Exporter;
use const COUNT_RECURSIVE;
use function bin2hex;
use function count;
use function get_resource_type;
use function gettype;
use function implode;
use function ini_get;
use function ini_set;
use function is_array;
use function is_bool;
use function is_float;
use function is_object;
use function is_resource;
use function is_string;
use function mb_strlen;
use function mb_substr;
use function preg_match;
use function spl_object_id;
use function sprintf;
use function str_repeat;
use function str_replace;
use function strtr;
use function var_export;
use BackedEnum;
use Google\Protobuf\Internal\Message;
use ReflectionObject;
use SebastianBergmann\RecursionContext\Context as RecursionContext;
use SplObjectStorage;
use UnitEnum;
final readonly class Exporter
{
/**
* @var non-negative-int
*/
private int $shortenArraysLongerThan;
/**
* @param non-negative-int $shortenArraysLongerThan
*/
public function __construct(int $shortenArraysLongerThan = 0)
{
$this->shortenArraysLongerThan = $shortenArraysLongerThan;
}
/**
* Exports a value as a string.
*
* The output of this method is similar to the output of print_r(), but
* improved in various aspects:
*
* - NULL is rendered as "null" (instead of "")
* - TRUE is rendered as "true" (instead of "1")
* - FALSE is rendered as "false" (instead of "")
* - Strings are always quoted with single quotes
* - Carriage returns and newlines are normalized to \n
* - Recursion and repeated rendering is treated properly
*/
public function export(mixed $value, int $indentation = 0): string
{
return $this->recursiveExport($value, $indentation);
}
/**
* @param array<mixed> $data
*/
public function shortenedRecursiveExport(array &$data, ?RecursionContext $processed = null): string
{
if (!$processed) {
$processed = new RecursionContext;
}
$overallCount = @count($data, COUNT_RECURSIVE);
$counter = 0;
$export = $this->shortenedCountedRecursiveExport($data, $processed, $counter);
if ($this->shortenArraysLongerThan > 0 &&
$overallCount > $this->shortenArraysLongerThan) {
$export .= sprintf(', ...%d more elements', $overallCount - $this->shortenArraysLongerThan);
}
return $export;
}
/**
* Exports a value into a single-line string.
*
* The output of this method is similar to the output of
* SebastianBergmann\Exporter\Exporter::export().
*
* Newlines are replaced by the visible string '\n'.
* Contents of arrays and objects (if any) are replaced by '...'.
*/
public function shortenedExport(mixed $value): string
{
if (is_string($value)) {
$string = str_replace("\n", '', $this->exportString($value));
if (mb_strlen($string) > 40) {
return mb_substr($string, 0, 30) . '...' . mb_substr($string, -7);
}
return $string;
}
if ($value instanceof BackedEnum) {
return sprintf(
'%s Enum (%s, %s)',
$value::class,
$value->name,
$this->export($value->value),
);
}
if ($value instanceof UnitEnum) {
return sprintf(
'%s Enum (%s)',
$value::class,
$value->name,
);
}
if (is_object($value)) {
return sprintf(
'%s Object (%s)',
$value::class,
$this->countProperties($value) > 0 ? '...' : '',
);
}
if (is_array($value)) {
return sprintf(
'[%s]',
count($value) > 0 ? '...' : '',
);
}
return $this->export($value);
}
/**
* Converts an object to an array containing all of its private, protected
* and public properties.
*
* @return array<mixed>
*/
public function toArray(mixed $value): array
{
if (!is_object($value)) {
return (array) $value;
}
$array = [];
foreach ((array) $value as $key => $val) {
// Exception traces commonly reference hundreds to thousands of
// objects currently loaded in memory. Including them in the result
// has a severe negative performance impact.
if ("\0Error\0trace" === $key || "\0Exception\0trace" === $key) {
continue;
}
// properties are transformed to keys in the following way:
// private $propertyName => "\0ClassName\0propertyName"
// protected $propertyName => "\0*\0propertyName"
// public $propertyName => "propertyName"
if (preg_match('/\0.+\0(.+)/', (string) $key, $matches)) {
$key = $matches[1];
}
// See https://github.com/php/php-src/commit/5721132
if ($key === "\0gcdata") {
continue;
}
$array[$key] = $val;
}
// Some internal classes like SplObjectStorage do not work with the
// above (fast) mechanism nor with reflection in Zend.
// Format the output similarly to print_r() in this case
if ($value instanceof SplObjectStorage) {
foreach ($value as $_value) {
$array['Object #' . spl_object_id($_value)] = [
'obj' => $_value,
'inf' => $value->getInfo(),
];
}
$value->rewind();
}
return $array;
}
public function countProperties(object $value): int
{
if ($this->canBeReflected($value)) {
$numberOfProperties = count((new ReflectionObject($value))->getProperties());
} else {
// @codeCoverageIgnoreStart
$numberOfProperties = count($this->toArray($value));
// @codeCoverageIgnoreEnd
}
return $numberOfProperties;
}
/**
* @param array<mixed> $data
*/
private function shortenedCountedRecursiveExport(array &$data, RecursionContext $processed, int &$counter): string
{
$result = [];
$array = $data;
/* @noinspection UnusedFunctionResultInspection */
$processed->add($data);
foreach ($array as $key => $value) {
if ($this->shortenArraysLongerThan > 0 &&
$counter > $this->shortenArraysLongerThan) {
break;
}
if (is_array($value)) {
if ($processed->contains($data[$key]) !== false) {
$result[] = '*RECURSION*';
} else {
$result[] = '[' . $this->shortenedCountedRecursiveExport($data[$key], $processed, $counter) . ']';
}
} else {
$result[] = $this->shortenedExport($value);
}
$counter++;
}
return implode(', ', $result);
}
private function recursiveExport(mixed &$value, int $indentation = 0, ?RecursionContext $processed = null): string
{
if ($value === null) {
return 'null';
}
if (is_bool($value)) {
return $value ? 'true' : 'false';
}
if (is_float($value)) {
return $this->exportFloat($value);
}
if (gettype($value) === 'resource (closed)') {
return 'resource (closed)';
}
/** @phpstan-ignore function.impossibleType */
if (is_resource($value)) {
return sprintf(
'resource(%d) of type (%s)',
(int) $value,
get_resource_type($value),
);
}
if ($value instanceof BackedEnum) {
return sprintf(
'%s Enum #%d (%s, %s)',
$value::class,
spl_object_id($value),
$value->name,
$this->export($value->value),
);
}
if ($value instanceof UnitEnum) {
return sprintf(
'%s Enum #%d (%s)',
$value::class,
spl_object_id($value),
$value->name,
);
}
if (is_string($value)) {
return $this->exportString($value);
}
if (!$processed) {
$processed = new RecursionContext;
}
if (is_array($value)) {
return $this->exportArray($value, $processed, $indentation);
}
if (is_object($value)) {
return $this->exportObject($value, $processed, $indentation);
}
return var_export($value, true);
}
private function exportFloat(float $value): string
{
$precisionBackup = ini_get('precision');
ini_set('precision', '-1');
$valueAsString = (string) $value;
ini_set('precision', $precisionBackup);
if ((string) (int) $value === $valueAsString) {
return $valueAsString . '.0';
}
return $valueAsString;
}
private function exportString(string $value): string
{
// Match for most non-printable chars somewhat taking multibyte chars into account
if (preg_match('/[^\x09-\x0d\x1b\x20-\xff]/', $value)) {
return 'Binary String: 0x' . bin2hex($value);
}
return "'" .
strtr(
$value,
[
"\r\n" => '\r\n' . "\n",
"\n\r" => '\n\r' . "\n",
"\r" => '\r' . "\n",
"\n" => '\n' . "\n",
],
) .
"'";
}
/**
* @param array<mixed> $value
*/
private function exportArray(array &$value, RecursionContext $processed, int $indentation): string
{
if (($key = $processed->contains($value)) !== false) {
return 'Array &' . $key;
}
$array = $value;
$key = $processed->add($value);
$values = '';
if (count($array) > 0) {
$whitespace = str_repeat(' ', 4 * $indentation);
foreach ($array as $k => $v) {
$values .=
$whitespace
. ' ' .
$this->recursiveExport($k, $indentation)
. ' => ' .
$this->recursiveExport($value[$k], $indentation + 1, $processed)
. ",\n";
}
$values = "\n" . $values . $whitespace;
}
return 'Array &' . (string) $key . ' [' . $values . ']';
}
private function exportObject(object $value, RecursionContext $processed, int $indentation): string
{
$class = $value::class;
if ($processed->contains($value) !== false) {
return $class . ' Object #' . spl_object_id($value);
}
$processed->add($value);
$array = $this->toArray($value);
$buffer = '';
if (count($array) > 0) {
$whitespace = str_repeat(' ', 4 * $indentation);
foreach ($array as $k => $v) {
$buffer .=
$whitespace
. ' ' .
$this->recursiveExport($k, $indentation)
. ' => ' .
$this->recursiveExport($v, $indentation + 1, $processed)
. ",\n";
}
$buffer = "\n" . $buffer . $whitespace;
}
return $class . ' Object #' . spl_object_id($value) . ' (' . $buffer . ')';
}
private function canBeReflected(object $object): bool
{
/** @phpstan-ignore class.notFound */
if ($object instanceof Message) {
// @codeCoverageIgnoreStart
return false;
// @codeCoverageIgnoreEnd
}
return true;
}
}

View File

@ -0,0 +1,129 @@
# Changes in sebastian/global-state
All notable changes in `sebastian/global-state` are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
## [7.0.2] - 2024-07-03
### Changed
* This project now uses PHPStan instead of Psalm for static analysis
## [7.0.1] - 2024-03-02
### Changed
* Do not use implicitly nullable parameters
## [7.0.0] - 2024-02-02
### Removed
* This component is no longer supported on PHP 8.1
## [6.0.2] - 2024-03-02
### Changed
* Do not use implicitly nullable parameters
## [6.0.1] - 2023-07-19
### Changed
* Changed usage of `ReflectionProperty::setValue()` to be compatible with PHP 8.3
## [6.0.0] - 2023-02-03
### Changed
* Renamed `SebastianBergmann\GlobalState\ExcludeList::addStaticAttribute()` to `SebastianBergmann\GlobalState\ExcludeList::addStaticProperty()`
* Renamed `SebastianBergmann\GlobalState\ExcludeList::isStaticAttributeExcluded()` to `SebastianBergmann\GlobalState\ExcludeList::isStaticPropertyExcluded()`
* Renamed `SebastianBergmann\GlobalState\Restorer::restoreStaticAttributes()` to `SebastianBergmann\GlobalState\Restorer::restoreStaticProperties()`
* Renamed `SebastianBergmann\GlobalState\Snapshot::staticAttributes()` to `SebastianBergmann\GlobalState\Snapshot::staticProperties()`
### Removed
* Removed `SebastianBergmann\GlobalState\Restorer::restoreFunctions()`
* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0
## [5.0.5] - 2022-02-14
### Fixed
* [#34](https://github.com/sebastianbergmann/global-state/pull/34): Uninitialised typed static properties are not handled correctly
## [5.0.4] - 2022-02-10
### Fixed
* The `$includeTraits` parameter of `SebastianBergmann\GlobalState\Snapshot::__construct()` is not respected
## [5.0.3] - 2021-06-11
### Changed
* `SebastianBergmann\GlobalState\CodeExporter::globalVariables()` now generates code that is compatible with PHP 8.1
## [5.0.2] - 2020-10-26
### Fixed
* `SebastianBergmann\GlobalState\Exception` now correctly extends `\Throwable`
## [5.0.1] - 2020-09-28
### Changed
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [5.0.0] - 2020-08-07
### Changed
* The `SebastianBergmann\GlobalState\Blacklist` class has been renamed to `SebastianBergmann\GlobalState\ExcludeList`
## [4.0.0] - 2020-02-07
### Removed
* This component is no longer supported on PHP 7.2
## [3.0.2] - 2022-02-10
### Fixed
* The `$includeTraits` parameter of `SebastianBergmann\GlobalState\Snapshot::__construct()` is not respected
## [3.0.1] - 2020-11-30
### Changed
* Changed PHP version constraint in `composer.json` from `^7.2` to `>=7.2`
## [3.0.0] - 2019-02-01
### Changed
* `Snapshot::canBeSerialized()` now recursively checks arrays and object graphs for variables that cannot be serialized
### Removed
* This component is no longer supported on PHP 7.0 and PHP 7.1
[7.0.2]: https://github.com/sebastianbergmann/global-state/compare/7.0.1...7.0.2
[7.0.1]: https://github.com/sebastianbergmann/global-state/compare/7.0.0...7.0.1
[7.0.0]: https://github.com/sebastianbergmann/global-state/compare/6.0...7.0.0
[6.0.2]: https://github.com/sebastianbergmann/global-state/compare/6.0.1...6.0.2
[6.0.1]: https://github.com/sebastianbergmann/global-state/compare/6.0.0...6.0.1
[6.0.0]: https://github.com/sebastianbergmann/global-state/compare/5.0.5...6.0.0
[5.0.5]: https://github.com/sebastianbergmann/global-state/compare/5.0.4...5.0.5
[5.0.4]: https://github.com/sebastianbergmann/global-state/compare/5.0.3...5.0.4
[5.0.3]: https://github.com/sebastianbergmann/global-state/compare/5.0.2...5.0.3
[5.0.2]: https://github.com/sebastianbergmann/global-state/compare/5.0.1...5.0.2
[5.0.1]: https://github.com/sebastianbergmann/global-state/compare/5.0.0...5.0.1
[5.0.0]: https://github.com/sebastianbergmann/global-state/compare/4.0.0...5.0.0
[4.0.0]: https://github.com/sebastianbergmann/global-state/compare/3.0.2...4.0.0
[3.0.2]: https://github.com/sebastianbergmann/phpunit/compare/3.0.1...3.0.2
[3.0.1]: https://github.com/sebastianbergmann/phpunit/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/sebastianbergmann/phpunit/compare/2.0.0...3.0.0

29
vendor/sebastian/global-state/LICENSE vendored Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2001-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21
vendor/sebastian/global-state/README.md vendored Normal file
View File

@ -0,0 +1,21 @@
[![Latest Stable Version](https://poser.pugx.org/sebastian/global-state/v/stable.png)](https://packagist.org/packages/sebastian/global-state)
[![CI Status](https://github.com/sebastianbergmann/global-state/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/global-state/actions)
[![codecov](https://codecov.io/gh/sebastianbergmann/global-state/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/global-state)
# sebastian/global-state
Snapshotting of global state, factored out of PHPUnit into a stand-alone component.
## Installation
You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/):
```
composer require sebastian/global-state
```
If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:
```
composer require --dev sebastian/global-state
```

View File

@ -0,0 +1,30 @@
# Security Policy
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email `sebastian@phpunit.de`.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
## Web Context
The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit.
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.

View File

@ -0,0 +1,52 @@
{
"name": "sebastian/global-state",
"description": "Snapshotting of global state",
"keywords": ["global state"],
"homepage": "https://www.github.com/sebastianbergmann/global-state",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
"security": "https://github.com/sebastianbergmann/global-state/security/policy"
},
"prefer-stable": true,
"config": {
"platform": {
"php": "8.2.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"require": {
"php": ">=8.2",
"sebastian/object-reflector": "^4.0",
"sebastian/recursion-context": "^6.0"
},
"require-dev": {
"ext-dom": "*",
"phpunit/phpunit": "^11.0"
},
"autoload": {
"classmap": [
"src/"
]
},
"autoload-dev": {
"classmap": [
"tests/_fixture/"
],
"files": [
"tests/_fixture/SnapshotFunctions.php"
]
},
"extra": {
"branch-alias": {
"dev-main": "7.0-dev"
}
}
}

View File

@ -0,0 +1,109 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/global-state.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\GlobalState;
use const PHP_EOL;
use function is_array;
use function is_scalar;
use function serialize;
use function sprintf;
use function var_export;
final class CodeExporter
{
public function constants(Snapshot $snapshot): string
{
$result = '';
foreach ($snapshot->constants() as $name => $value) {
$result .= sprintf(
'if (!defined(\'%s\')) define(\'%s\', %s);' . "\n",
$name,
$name,
$this->exportVariable($value),
);
}
return $result;
}
public function globalVariables(Snapshot $snapshot): string
{
$result = <<<'EOT'
call_user_func(
function ()
{
foreach (array_keys($GLOBALS) as $key) {
unset($GLOBALS[$key]);
}
}
);
EOT;
foreach ($snapshot->globalVariables() as $name => $value) {
$result .= sprintf(
'$GLOBALS[%s] = %s;' . PHP_EOL,
$this->exportVariable($name),
$this->exportVariable($value),
);
}
return $result;
}
public function iniSettings(Snapshot $snapshot): string
{
$result = '';
foreach ($snapshot->iniSettings() as $key => $value) {
$result .= sprintf(
'@ini_set(%s, %s);' . "\n",
$this->exportVariable($key),
$this->exportVariable($value),
);
}
return $result;
}
private function exportVariable(mixed $variable): string
{
if (is_scalar($variable) || null === $variable ||
(is_array($variable) && $this->arrayOnlyContainsScalars($variable))) {
return var_export($variable, true);
}
return 'unserialize(' . var_export(serialize($variable), true) . ')';
}
/**
* @param array<mixed> $array
*/
private function arrayOnlyContainsScalars(array $array): bool
{
$result = true;
foreach ($array as $element) {
if (is_array($element)) {
$result = $this->arrayOnlyContainsScalars($element);
} elseif (!is_scalar($element) && null !== $element) {
$result = false;
}
if ($result === false) {
break;
}
}
return $result;
}
}

View File

@ -0,0 +1,138 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/global-state.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\GlobalState;
use function in_array;
use function str_starts_with;
use ReflectionClass;
final class ExcludeList
{
/**
* @var array<non-empty-string, true>
*/
private array $globalVariables = [];
/**
* @var list<non-empty-string>
*/
private array $classes = [];
/**
* @var list<non-empty-string>
*/
private array $classNamePrefixes = [];
/**
* @var list<non-empty-string>
*/
private array $parentClasses = [];
/**
* @var list<non-empty-string>
*/
private array $interfaces = [];
/**
* @var array<string, array<non-empty-string, true>>
*/
private array $staticProperties = [];
/**
* @param non-empty-string $variableName
*/
public function addGlobalVariable(string $variableName): void
{
$this->globalVariables[$variableName] = true;
}
/**
* @param non-empty-string $className
*/
public function addClass(string $className): void
{
$this->classes[] = $className;
}
/**
* @param non-empty-string $className
*/
public function addSubclassesOf(string $className): void
{
$this->parentClasses[] = $className;
}
/**
* @param non-empty-string $interfaceName
*/
public function addImplementorsOf(string $interfaceName): void
{
$this->interfaces[] = $interfaceName;
}
/**
* @param non-empty-string $classNamePrefix
*/
public function addClassNamePrefix(string $classNamePrefix): void
{
$this->classNamePrefixes[] = $classNamePrefix;
}
/**
* @param non-empty-string $className
* @param non-empty-string $propertyName
*/
public function addStaticProperty(string $className, string $propertyName): void
{
if (!isset($this->staticProperties[$className])) {
$this->staticProperties[$className] = [];
}
$this->staticProperties[$className][$propertyName] = true;
}
public function isGlobalVariableExcluded(string $variableName): bool
{
return isset($this->globalVariables[$variableName]);
}
/**
* @param class-string $className
* @param non-empty-string $propertyName
*/
public function isStaticPropertyExcluded(string $className, string $propertyName): bool
{
if (in_array($className, $this->classes, true)) {
return true;
}
foreach ($this->classNamePrefixes as $prefix) {
if (str_starts_with($className, $prefix)) {
return true;
}
}
$class = new ReflectionClass($className);
foreach ($this->parentClasses as $type) {
if ($class->isSubclassOf($type)) {
return true;
}
}
foreach ($this->interfaces as $type) {
if ($class->implementsInterface($type)) {
return true;
}
}
return isset($this->staticProperties[$className][$propertyName]);
}
}

View File

@ -0,0 +1,106 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/global-state.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\GlobalState;
use function array_diff;
use function array_key_exists;
use function array_keys;
use function array_merge;
use function in_array;
use function is_array;
use ReflectionClass;
use ReflectionProperty;
final class Restorer
{
public function restoreGlobalVariables(Snapshot $snapshot): void
{
$superGlobalArrays = $snapshot->superGlobalArrays();
foreach ($superGlobalArrays as $superGlobalArray) {
$this->restoreSuperGlobalArray($snapshot, $superGlobalArray);
}
$globalVariables = $snapshot->globalVariables();
foreach (array_keys($GLOBALS) as $key) {
if ($key !== 'GLOBALS' &&
!in_array($key, $superGlobalArrays, true) &&
!$snapshot->excludeList()->isGlobalVariableExcluded((string) $key)) {
if (array_key_exists($key, $globalVariables)) {
$GLOBALS[$key] = $globalVariables[$key];
} else {
unset($GLOBALS[$key]);
}
}
}
}
public function restoreStaticProperties(Snapshot $snapshot): void
{
$current = new Snapshot($snapshot->excludeList(), false, false, false, false, true, false, false, false, false);
$newClasses = array_diff($current->classes(), $snapshot->classes());
unset($current);
foreach ($snapshot->staticProperties() as $className => $staticProperties) {
foreach ($staticProperties as $name => $value) {
$reflector = new ReflectionProperty($className, $name);
$reflector->setValue(null, $value);
}
}
foreach ($newClasses as $className) {
$class = new ReflectionClass($className);
$defaults = $class->getDefaultProperties();
foreach ($class->getProperties() as $property) {
if (!$property->isStatic()) {
continue;
}
$name = $property->getName();
if ($snapshot->excludeList()->isStaticPropertyExcluded($className, $name)) {
continue;
}
if (!isset($defaults[$name])) {
continue;
}
$property->setValue(null, $defaults[$name]);
}
}
}
private function restoreSuperGlobalArray(Snapshot $snapshot, string $superGlobalArray): void
{
$superGlobalVariables = $snapshot->superGlobalVariables();
if (isset($GLOBALS[$superGlobalArray], $superGlobalVariables[$superGlobalArray]) &&
is_array($GLOBALS[$superGlobalArray])) {
$keys = array_keys(
array_merge(
$GLOBALS[$superGlobalArray],
$superGlobalVariables[$superGlobalArray],
),
);
foreach ($keys as $key) {
if (isset($superGlobalVariables[$superGlobalArray][$key])) {
$GLOBALS[$superGlobalArray][$key] = $superGlobalVariables[$superGlobalArray][$key];
} else {
unset($GLOBALS[$superGlobalArray][$key]);
}
}
}
}
}

View File

@ -0,0 +1,447 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/global-state.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\GlobalState;
use function array_keys;
use function array_merge;
use function array_reverse;
use function assert;
use function get_declared_classes;
use function get_declared_interfaces;
use function get_declared_traits;
use function get_defined_constants;
use function get_defined_functions;
use function get_included_files;
use function in_array;
use function ini_get_all;
use function is_array;
use function is_object;
use function is_resource;
use function is_scalar;
use function serialize;
use function unserialize;
use ReflectionClass;
use SebastianBergmann\ObjectReflector\ObjectReflector;
use SebastianBergmann\RecursionContext\Context;
use Throwable;
/**
* A snapshot of global state.
*/
final class Snapshot
{
private ExcludeList $excludeList;
/**
* @var array<string, mixed>
*/
private array $globalVariables = [];
/**
* @var list<string>
*/
private array $superGlobalArrays = [];
/**
* @var array<string, array<string, mixed>>
*/
private array $superGlobalVariables = [];
/**
* @var array<string, array<string, mixed>>
*/
private array $staticProperties = [];
/**
* @var array<non-empty-string, array{global_value: string, local_value: string, access: int}>
*/
private array $iniSettings = [];
/**
* @var list<string>
*/
private array $includedFiles = [];
/**
* @var array<string, mixed>
*/
private array $constants = [];
/**
* @var list<callable-string>
*/
private array $functions = [];
/**
* @var list<class-string>
*/
private array $interfaces = [];
/**
* @var list<class-string>
*/
private array $classes = [];
/**
* @var list<class-string>
*/
private array $traits = [];
public function __construct(?ExcludeList $excludeList = null, bool $includeGlobalVariables = true, bool $includeStaticProperties = true, bool $includeConstants = true, bool $includeFunctions = true, bool $includeClasses = true, bool $includeInterfaces = true, bool $includeTraits = true, bool $includeIniSettings = true, bool $includeIncludedFiles = true)
{
$this->excludeList = $excludeList ?: new ExcludeList;
if ($includeConstants) {
$this->snapshotConstants();
}
if ($includeFunctions) {
$this->snapshotFunctions();
}
if ($includeClasses || $includeStaticProperties) {
$this->snapshotClasses();
}
if ($includeInterfaces) {
$this->snapshotInterfaces();
}
if ($includeGlobalVariables) {
$this->setupSuperGlobalArrays();
$this->snapshotGlobals();
}
if ($includeStaticProperties) {
$this->snapshotStaticProperties();
}
if ($includeIniSettings) {
$iniSettings = ini_get_all(null, false);
assert($iniSettings !== false);
$this->iniSettings = $iniSettings;
}
if ($includeIncludedFiles) {
$this->includedFiles = get_included_files();
}
if ($includeTraits) {
$this->traits = get_declared_traits();
}
}
public function excludeList(): ExcludeList
{
return $this->excludeList;
}
/**
* @return array<string, mixed>
*/
public function globalVariables(): array
{
return $this->globalVariables;
}
/**
* @return array<string, array<string, mixed>>
*/
public function superGlobalVariables(): array
{
return $this->superGlobalVariables;
}
/**
* @return list<string>
*/
public function superGlobalArrays(): array
{
return $this->superGlobalArrays;
}
/**
* @return array<string, array<string, mixed>>
*/
public function staticProperties(): array
{
return $this->staticProperties;
}
/**
* @return array<non-empty-string, array{global_value: string, local_value: string, access: int}>
*/
public function iniSettings(): array
{
return $this->iniSettings;
}
/**
* @return list<string>
*/
public function includedFiles(): array
{
return $this->includedFiles;
}
/**
* @return array<string, mixed>
*/
public function constants(): array
{
return $this->constants;
}
/**
* @return list<callable-string>
*/
public function functions(): array
{
return $this->functions;
}
/**
* @return list<class-string>
*/
public function interfaces(): array
{
return $this->interfaces;
}
/**
* @return list<class-string>
*/
public function classes(): array
{
return $this->classes;
}
/**
* @return list<class-string>
*/
public function traits(): array
{
return $this->traits;
}
private function snapshotConstants(): void
{
$constants = get_defined_constants(true);
if (isset($constants['user'])) {
$this->constants = $constants['user'];
}
}
private function snapshotFunctions(): void
{
$functions = get_defined_functions();
$this->functions = $functions['user'];
}
private function snapshotClasses(): void
{
foreach (array_reverse(get_declared_classes()) as $className) {
$class = new ReflectionClass($className);
if (!$class->isUserDefined()) {
break;
}
$this->classes[] = $className;
}
$this->classes = array_reverse($this->classes);
}
private function snapshotInterfaces(): void
{
foreach (array_reverse(get_declared_interfaces()) as $interfaceName) {
$class = new ReflectionClass($interfaceName);
if (!$class->isUserDefined()) {
break;
}
$this->interfaces[] = $interfaceName;
}
$this->interfaces = array_reverse($this->interfaces);
}
private function snapshotGlobals(): void
{
$superGlobalArrays = $this->superGlobalArrays();
foreach ($superGlobalArrays as $superGlobalArray) {
$this->snapshotSuperGlobalArray($superGlobalArray);
}
foreach (array_keys($GLOBALS) as $key) {
if ($key !== 'GLOBALS' &&
!in_array($key, $superGlobalArrays, true) &&
$this->canBeSerialized($GLOBALS[$key]) &&
!$this->excludeList->isGlobalVariableExcluded($key)) {
/* @noinspection UnserializeExploitsInspection */
$this->globalVariables[$key] = unserialize(serialize($GLOBALS[$key]));
}
}
}
private function snapshotSuperGlobalArray(string $superGlobalArray): void
{
$this->superGlobalVariables[$superGlobalArray] = [];
if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])) {
foreach ($GLOBALS[$superGlobalArray] as $key => $value) {
/* @noinspection UnserializeExploitsInspection */
$this->superGlobalVariables[$superGlobalArray][$key] = unserialize(serialize($value));
}
}
}
private function snapshotStaticProperties(): void
{
foreach ($this->classes as $className) {
$class = new ReflectionClass($className);
$snapshot = [];
foreach ($class->getProperties() as $property) {
if ($property->isStatic()) {
$name = $property->getName();
if ($this->excludeList->isStaticPropertyExcluded($className, $name)) {
continue;
}
if (!$property->isInitialized()) {
continue;
}
$value = $property->getValue();
if ($this->canBeSerialized($value)) {
/* @noinspection UnserializeExploitsInspection */
$snapshot[$name] = unserialize(serialize($value));
}
}
}
if (!empty($snapshot)) {
$this->staticProperties[$className] = $snapshot;
}
}
}
private function setupSuperGlobalArrays(): void
{
$this->superGlobalArrays = [
'_ENV',
'_POST',
'_GET',
'_COOKIE',
'_SERVER',
'_FILES',
'_REQUEST',
];
}
private function canBeSerialized(mixed $variable): bool
{
if (is_scalar($variable) || $variable === null) {
return true;
}
if (is_resource($variable)) {
return false;
}
foreach ($this->enumerateObjectsAndResources($variable) as $value) {
if (is_resource($value)) {
return false;
}
if (is_object($value)) {
$class = new ReflectionClass($value);
if ($class->isAnonymous()) {
return false;
}
try {
@serialize($value);
} catch (Throwable $t) {
return false;
}
}
}
return true;
}
/**
* @return array<mixed>
*/
private function enumerateObjectsAndResources(mixed $variable, Context $processed = new Context): array
{
$result = [];
if ($processed->contains($variable)) {
return $result;
}
$array = $variable;
/* @noinspection UnusedFunctionResultInspection */
$processed->add($variable);
if (is_array($variable)) {
/** @phpstan-ignore foreach.nonIterable */
foreach ($array as $element) {
if (!is_array($element) && !is_object($element) && !is_resource($element)) {
continue;
}
if (!is_resource($element)) {
/** @noinspection SlowArrayOperationsInLoopInspection */
$result = array_merge(
$result,
$this->enumerateObjectsAndResources($element, $processed),
);
} else {
$result[] = $element;
}
}
} else {
$result[] = $variable;
foreach ((new ObjectReflector)->getProperties($variable) as $value) {
if (!is_array($value) && !is_object($value) && !is_resource($value)) {
continue;
}
if (!is_resource($value)) {
/** @noinspection SlowArrayOperationsInLoopInspection */
$result = array_merge(
$result,
$this->enumerateObjectsAndResources($value, $processed),
);
} else {
$result[] = $value;
}
}
}
return $result;
}
}

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/global-state.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\GlobalState;
use Throwable;
interface Exception extends Throwable
{
}

View File

@ -0,0 +1,14 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/global-state.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\GlobalState;
final class RuntimeException extends \RuntimeException implements Exception
{
}

View File

@ -0,0 +1,70 @@
# ChangeLog
All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
## [3.0.1] - 2024-07-03
### Changed
* This project now uses PHPStan instead of Psalm for static analysis
## [3.0.0] - 2024-02-02
### Removed
* This component now requires PHP-Parser 5
* This component is no longer supported on PHP 8.1
## [2.0.2] - 2023-12-21
### Changed
* This component is now compatible with `nikic/php-parser` 5.0
## [2.0.1] - 2023-08-31
### Changed
* Improved type information
## [2.0.0] - 2023-02-03
### Removed
* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0
## [1.0.3] - 2020-11-28
### Fixed
* Files that do not contain a newline were not handled correctly
### Changed
* A line of code is no longer considered to be a Logical Line of Code if it does not contain an `Expr` node
## [1.0.2] - 2020-10-26
### Fixed
* `SebastianBergmann\LinesOfCode\Exception` now correctly extends `\Throwable`
## [1.0.1] - 2020-09-28
### Changed
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [1.0.0] - 2020-07-22
* Initial release
[3.0.1]: https://github.com/sebastianbergmann/lines-of-code/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/sebastianbergmann/lines-of-code/compare/2.0...3.0.0
[2.0.2]: https://github.com/sebastianbergmann/lines-of-code/compare/2.0.1...2.0.2
[2.0.1]: https://github.com/sebastianbergmann/lines-of-code/compare/2.0.0...2.0.1
[2.0.0]: https://github.com/sebastianbergmann/lines-of-code/compare/1.0.3...2.0.0
[1.0.3]: https://github.com/sebastianbergmann/lines-of-code/compare/1.0.2...1.0.3
[1.0.2]: https://github.com/sebastianbergmann/lines-of-code/compare/1.0.1...1.0.2
[1.0.1]: https://github.com/sebastianbergmann/lines-of-code/compare/1.0.0...1.0.1
[1.0.0]: https://github.com/sebastianbergmann/lines-of-code/compare/f959e71f00e591288acc024afe9cb966c6cf9bd6...1.0.0

29
vendor/sebastian/lines-of-code/LICENSE vendored Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2020-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,21 @@
[![Latest Stable Version](https://poser.pugx.org/sebastian/lines-of-code/v/stable.png)](https://packagist.org/packages/sebastian/lines-of-code)
[![CI Status](https://github.com/sebastianbergmann/lines-of-code/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/lines-of-code/actions)
[![codecov](https://codecov.io/gh/sebastianbergmann/lines-of-code/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/lines-of-code)
# sebastian/lines-of-code
Library for counting the lines of code in PHP source code.
## Installation
You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/):
```
composer require sebastian/lines-of-code
```
If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:
```
composer require --dev sebastian/lines-of-code
```

View File

@ -0,0 +1,30 @@
# Security Policy
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email `sebastian@phpunit.de`.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
## Web Context
The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit.
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.

View File

@ -0,0 +1,43 @@
{
"name": "sebastian/lines-of-code",
"description": "Library for counting the lines of code in PHP source code",
"type": "library",
"homepage": "https://github.com/sebastianbergmann/lines-of-code",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"support": {
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
"security": "https://github.com/sebastianbergmann/lines-of-code/security/policy"
},
"prefer-stable": true,
"require": {
"php": ">=8.2",
"nikic/php-parser": "^5.0"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
},
"config": {
"platform": {
"php": "8.2"
},
"optimize-autoloader": true,
"sort-packages": true
},
"autoload": {
"classmap": [
"src/"
]
},
"extra": {
"branch-alias": {
"dev-main": "3.0-dev"
}
}
}

View File

@ -0,0 +1,91 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/lines-of-code.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\LinesOfCode;
use function assert;
use function file_get_contents;
use function substr_count;
use PhpParser\Error;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\ParserFactory;
final class Counter
{
/**
* @throws RuntimeException
*/
public function countInSourceFile(string $sourceFile): LinesOfCode
{
$source = file_get_contents($sourceFile);
assert($source !== false);
return $this->countInSourceString($source);
}
/**
* @throws RuntimeException
*/
public function countInSourceString(string $source): LinesOfCode
{
$linesOfCode = substr_count($source, "\n");
if ($linesOfCode === 0 && !empty($source)) {
$linesOfCode = 1;
}
try {
$nodes = (new ParserFactory)->createForHostVersion()->parse($source);
assert($nodes !== null);
return $this->countInAbstractSyntaxTree($linesOfCode, $nodes);
// @codeCoverageIgnoreStart
} catch (Error $error) {
throw new RuntimeException(
$error->getMessage(),
$error->getCode(),
$error,
);
}
// @codeCoverageIgnoreEnd
}
/**
* @param non-negative-int $linesOfCode
* @param Node[] $nodes
*
* @throws RuntimeException
*/
public function countInAbstractSyntaxTree(int $linesOfCode, array $nodes): LinesOfCode
{
$traverser = new NodeTraverser;
$visitor = new LineCountingVisitor($linesOfCode);
$traverser->addVisitor($visitor);
try {
/* @noinspection UnusedFunctionResultInspection */
$traverser->traverse($nodes);
// @codeCoverageIgnoreStart
} catch (Error $error) {
throw new RuntimeException(
$error->getMessage(),
$error->getCode(),
$error,
);
}
// @codeCoverageIgnoreEnd
return $visitor->result();
}
}

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/lines-of-code.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\LinesOfCode;
use Throwable;
interface Exception extends Throwable
{
}

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/lines-of-code.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\LinesOfCode;
use LogicException;
final class IllogicalValuesException extends LogicException implements Exception
{
}

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/lines-of-code.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\LinesOfCode;
use InvalidArgumentException;
final class NegativeValueException extends InvalidArgumentException implements Exception
{
}

View File

@ -0,0 +1,14 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/lines-of-code.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\LinesOfCode;
final class RuntimeException extends \RuntimeException implements Exception
{
}

View File

@ -0,0 +1,92 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/lines-of-code.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\LinesOfCode;
use function array_merge;
use function array_unique;
use function assert;
use function count;
use PhpParser\Comment;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\NodeVisitorAbstract;
final class LineCountingVisitor extends NodeVisitorAbstract
{
/**
* @var non-negative-int
*/
private readonly int $linesOfCode;
/**
* @var Comment[]
*/
private array $comments = [];
/**
* @var int[]
*/
private array $linesWithStatements = [];
/**
* @param non-negative-int $linesOfCode
*/
public function __construct(int $linesOfCode)
{
$this->linesOfCode = $linesOfCode;
}
public function enterNode(Node $node): void
{
$this->comments = array_merge($this->comments, $node->getComments());
if (!$node instanceof Expr) {
return;
}
$this->linesWithStatements[] = $node->getStartLine();
}
public function result(): LinesOfCode
{
$commentLinesOfCode = 0;
foreach ($this->comments() as $comment) {
$commentLinesOfCode += ($comment->getEndLine() - $comment->getStartLine() + 1);
}
$nonCommentLinesOfCode = $this->linesOfCode - $commentLinesOfCode;
$logicalLinesOfCode = count(array_unique($this->linesWithStatements));
assert($commentLinesOfCode >= 0);
assert($nonCommentLinesOfCode >= 0);
return new LinesOfCode(
$this->linesOfCode,
$commentLinesOfCode,
$nonCommentLinesOfCode,
$logicalLinesOfCode,
);
}
/**
* @return Comment[]
*/
private function comments(): array
{
$comments = [];
foreach ($this->comments as $comment) {
$comments[$comment->getStartLine() . '_' . $comment->getStartTokenPos() . '_' . $comment->getEndLine() . '_' . $comment->getEndTokenPos()] = $comment;
}
return $comments;
}
}

View File

@ -0,0 +1,119 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/lines-of-code.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\LinesOfCode;
/**
* @immutable
*/
final readonly class LinesOfCode
{
/**
* @var non-negative-int
*/
private int $linesOfCode;
/**
* @var non-negative-int
*/
private int $commentLinesOfCode;
/**
* @var non-negative-int
*/
private int $nonCommentLinesOfCode;
/**
* @var non-negative-int
*/
private int $logicalLinesOfCode;
/**
* @param non-negative-int $linesOfCode
* @param non-negative-int $commentLinesOfCode
* @param non-negative-int $nonCommentLinesOfCode
* @param non-negative-int $logicalLinesOfCode
*
* @throws IllogicalValuesException
* @throws NegativeValueException
*/
public function __construct(int $linesOfCode, int $commentLinesOfCode, int $nonCommentLinesOfCode, int $logicalLinesOfCode)
{
/** @phpstan-ignore smaller.alwaysFalse */
if ($linesOfCode < 0) {
throw new NegativeValueException('$linesOfCode must not be negative');
}
/** @phpstan-ignore smaller.alwaysFalse */
if ($commentLinesOfCode < 0) {
throw new NegativeValueException('$commentLinesOfCode must not be negative');
}
/** @phpstan-ignore smaller.alwaysFalse */
if ($nonCommentLinesOfCode < 0) {
throw new NegativeValueException('$nonCommentLinesOfCode must not be negative');
}
/** @phpstan-ignore smaller.alwaysFalse */
if ($logicalLinesOfCode < 0) {
throw new NegativeValueException('$logicalLinesOfCode must not be negative');
}
if ($linesOfCode - $commentLinesOfCode !== $nonCommentLinesOfCode) {
throw new IllogicalValuesException('$linesOfCode !== $commentLinesOfCode + $nonCommentLinesOfCode');
}
$this->linesOfCode = $linesOfCode;
$this->commentLinesOfCode = $commentLinesOfCode;
$this->nonCommentLinesOfCode = $nonCommentLinesOfCode;
$this->logicalLinesOfCode = $logicalLinesOfCode;
}
/**
* @return non-negative-int
*/
public function linesOfCode(): int
{
return $this->linesOfCode;
}
/**
* @return non-negative-int
*/
public function commentLinesOfCode(): int
{
return $this->commentLinesOfCode;
}
/**
* @return non-negative-int
*/
public function nonCommentLinesOfCode(): int
{
return $this->nonCommentLinesOfCode;
}
/**
* @return non-negative-int
*/
public function logicalLinesOfCode(): int
{
return $this->logicalLinesOfCode;
}
public function plus(self $other): self
{
return new self(
$this->linesOfCode() + $other->linesOfCode(),
$this->commentLinesOfCode() + $other->commentLinesOfCode(),
$this->nonCommentLinesOfCode() + $other->nonCommentLinesOfCode(),
$this->logicalLinesOfCode() + $other->logicalLinesOfCode(),
);
}
}

View File

@ -0,0 +1,109 @@
# Change Log
All notable changes to `sebastianbergmann/object-enumerator` are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [6.0.1] - 2024-07-03
### Changed
* This project now uses PHPStan instead of Psalm for static analysis
## [6.0.0] - 2024-02-02
### Removed
* This component is no longer supported on PHP 8.1
## [5.0.0] - 2023-02-03
### Removed
* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0
## [4.0.4] - 2020-10-26
### Fixed
* `SebastianBergmann\ObjectEnumerator\Exception` now correctly extends `\Throwable`
## [4.0.3] - 2020-09-28
### Changed
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [4.0.2] - 2020-06-26
### Added
* This component is now supported on PHP 8
## [4.0.1] - 2020-06-15
### Changed
* Tests etc. are now ignored for archive exports
## [4.0.0] - 2020-02-07
### Removed
* This component is no longer supported on PHP 7.0, PHP 7.1, and PHP 7.2
## [3.0.3] - 2017-08-03
### Changed
* Bumped required version of `sebastian/object-reflector`
## [3.0.2] - 2017-03-12
### Changed
* `sebastian/object-reflector` is now a dependency
## [3.0.1] - 2017-03-12
### Fixed
* Objects aggregated in inherited attributes are not enumerated
## [3.0.0] - 2017-03-03
### Removed
* This component is no longer supported on PHP 5.6
## [2.0.1] - 2017-02-18
### Fixed
* Fixed [#2](https://github.com/sebastianbergmann/phpunit/pull/2): Exceptions in `ReflectionProperty::getValue()` are not handled
## [2.0.0] - 2016-11-19
### Changed
* This component is now compatible with `sebastian/recursion-context: ~1.0.4`
## 1.0.0 - 2016-02-04
### Added
* Initial release
[6.0.1]: https://github.com/sebastianbergmann/object-enumerator/compare/6.0.0...6.0.1
[6.0.0]: https://github.com/sebastianbergmann/object-enumerator/compare/5.0...6.0.0
[5.0.0]: https://github.com/sebastianbergmann/object-enumerator/compare/4.0.4...5.0.0
[4.0.4]: https://github.com/sebastianbergmann/object-enumerator/compare/4.0.3...4.0.4
[4.0.3]: https://github.com/sebastianbergmann/object-enumerator/compare/4.0.2...4.0.3
[4.0.2]: https://github.com/sebastianbergmann/object-enumerator/compare/4.0.1...4.0.2
[4.0.1]: https://github.com/sebastianbergmann/object-enumerator/compare/4.0.0...4.0.1
[4.0.0]: https://github.com/sebastianbergmann/object-enumerator/compare/3.0.3...4.0.0
[3.0.3]: https://github.com/sebastianbergmann/object-enumerator/compare/3.0.2...3.0.3
[3.0.2]: https://github.com/sebastianbergmann/object-enumerator/compare/3.0.1...3.0.2
[3.0.1]: https://github.com/sebastianbergmann/object-enumerator/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/sebastianbergmann/object-enumerator/compare/2.0...3.0.0
[2.0.1]: https://github.com/sebastianbergmann/object-enumerator/compare/2.0.0...2.0.1
[2.0.0]: https://github.com/sebastianbergmann/object-enumerator/compare/1.0...2.0.0

View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2016-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,21 @@
[![Latest Stable Version](https://poser.pugx.org/sebastian/object-enumerator/v/stable.png)](https://packagist.org/packages/sebastian/object-enumerator)
[![CI Status](https://github.com/sebastianbergmann/object-enumerator/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/object-enumerator/actions)
[![codecov](https://codecov.io/gh/sebastianbergmann/object-enumerator/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/object-enumerator)
# sebastian/object-enumerator
Traverses array structures and object graphs to enumerate all referenced objects.
## Installation
You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/):
```
composer require sebastian/object-enumerator
```
If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:
```
composer require --dev sebastian/object-enumerator
```

View File

@ -0,0 +1,30 @@
# Security Policy
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email `sebastian@phpunit.de`.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
## Web Context
The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit.
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.

View File

@ -0,0 +1,47 @@
{
"name": "sebastian/object-enumerator",
"description": "Traverses array structures and object graphs to enumerate all referenced objects",
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"support": {
"issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
"security": "https://github.com/sebastianbergmann/object-enumerator/security/policy"
},
"prefer-stable": true,
"config": {
"platform": {
"php": "8.2.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"require": {
"php": ">=8.2",
"sebastian/object-reflector": "^4.0",
"sebastian/recursion-context": "^6.0"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
},
"autoload": {
"classmap": [
"src/"
]
},
"autoload-dev": {
"classmap": [
"tests/_fixture/"
]
},
"extra": {
"branch-alias": {
"dev-main": "6.0-dev"
}
}
}

View File

@ -0,0 +1,71 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/object-enumerator.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\ObjectEnumerator;
use function array_merge;
use function is_array;
use function is_object;
use SebastianBergmann\ObjectReflector\ObjectReflector;
use SebastianBergmann\RecursionContext\Context;
final class Enumerator
{
/**
* @param array<mixed>|object $variable
*
* @return list<object>
*/
public function enumerate(array|object $variable, Context $processed = new Context): array
{
$objects = [];
if ($processed->contains($variable)) {
return $objects;
}
$array = $variable;
/* @noinspection UnusedFunctionResultInspection */
$processed->add($variable);
if (is_array($variable)) {
/** @phpstan-ignore foreach.nonIterable */
foreach ($array as $element) {
if (!is_array($element) && !is_object($element)) {
continue;
}
/** @noinspection SlowArrayOperationsInLoopInspection */
$objects = array_merge(
$objects,
$this->enumerate($element, $processed),
);
}
return $objects;
}
$objects[] = $variable;
foreach ((new ObjectReflector)->getProperties($variable) as $value) {
if (!is_array($value) && !is_object($value)) {
continue;
}
/** @noinspection SlowArrayOperationsInLoopInspection */
$objects = array_merge(
$objects,
$this->enumerate($value, $processed),
);
}
return $objects;
}
}

View File

@ -0,0 +1,80 @@
# Change Log
All notable changes to `sebastianbergmann/object-reflector` are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [4.0.1] - 2024-07-03
### Changed
* This project now uses PHPStan instead of Psalm for static analysis
## [4.0.0] - 2024-02-02
### Removed
* This component is no longer supported on PHP 8.1
## [3.0.0] - 2023-02-03
### Changed
* `ObjectReflector::getAttributes()` has been renamed to `ObjectReflector::getProperties()`
### Removed
* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0
## [2.0.4] - 2020-10-26
### Fixed
* `SebastianBergmann\ObjectReflector\Exception` now correctly extends `\Throwable`
## [2.0.3] - 2020-09-28
### Changed
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [2.0.2] - 2020-06-26
### Added
* This component is now supported on PHP 8
## [2.0.1] - 2020-06-15
### Changed
* Tests etc. are now ignored for archive exports
## [2.0.0] - 2020-02-07
### Removed
* This component is no longer supported on PHP 7.0, PHP 7.1, and PHP 7.2
## [1.1.1] - 2017-03-29
* Fixed [#1](https://github.com/sebastianbergmann/object-reflector/issues/1): Attributes with non-string names are not handled correctly
## [1.1.0] - 2017-03-16
### Changed
* Changed implementation of `ObjectReflector::getattributes()` to use `(array)` cast instead of `ReflectionObject`
## 1.0.0 - 2017-03-12
* Initial release
[4.0.1]: https://github.com/sebastianbergmann/object-reflector/compare/4.0.0...4.0.1
[4.0.0]: https://github.com/sebastianbergmann/object-reflector/compare/3.0...4.0.0
[3.0.0]: https://github.com/sebastianbergmann/object-reflector/compare/2.0.4...3.0.0
[2.0.4]: https://github.com/sebastianbergmann/object-reflector/compare/2.0.3...2.0.4
[2.0.3]: https://github.com/sebastianbergmann/object-reflector/compare/2.0.2...2.0.3
[2.0.2]: https://github.com/sebastianbergmann/object-reflector/compare/2.0.1...2.0.2
[2.0.1]: https://github.com/sebastianbergmann/object-reflector/compare/2.0.0...2.0.1
[2.0.0]: https://github.com/sebastianbergmann/object-reflector/compare/1.1.1...2.0.0
[1.1.1]: https://github.com/sebastianbergmann/object-reflector/compare/1.1.0...1.1.1
[1.1.0]: https://github.com/sebastianbergmann/object-reflector/compare/1.0.0...1.1.0

View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2017-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,21 @@
[![Latest Stable Version](https://poser.pugx.org/sebastian/object-reflector/v/stable.png)](https://packagist.org/packages/sebastian/object-reflector)
[![CI Status](https://github.com/sebastianbergmann/object-reflector/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/object-reflector/actions)
[![codecov](https://codecov.io/gh/sebastianbergmann/object-reflector/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/object-reflector)
# sebastian/object-reflector
Allows reflection of object properties, including inherited and private as well as protected ones.
## Installation
You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/):
```
composer require sebastian/object-reflector
```
If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:
```
composer require --dev sebastian/object-reflector
```

View File

@ -0,0 +1,30 @@
# Security Policy
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email `sebastian@phpunit.de`.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
## Web Context
The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit.
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.

View File

@ -0,0 +1,45 @@
{
"name": "sebastian/object-reflector",
"description": "Allows reflection of object attributes, including inherited and non-public ones",
"homepage": "https://github.com/sebastianbergmann/object-reflector/",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"support": {
"issues": "https://github.com/sebastianbergmann/object-reflector/issues",
"security": "https://github.com/sebastianbergmann/object-reflector/security/policy"
},
"prefer-stable": true,
"config": {
"platform": {
"php": "8.2.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"require": {
"php": ">=8.2"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
},
"autoload": {
"classmap": [
"src/"
]
},
"autoload-dev": {
"classmap": [
"tests/_fixture/"
]
},
"extra": {
"branch-alias": {
"dev-main": "4.0-dev"
}
}
}

View File

@ -0,0 +1,41 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/object-reflector.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\ObjectReflector;
use function count;
use function explode;
final class ObjectReflector
{
/**
* @return array<string, mixed>
*/
public function getProperties(object $object): array
{
$properties = [];
$className = $object::class;
foreach ((array) $object as $name => $value) {
$name = explode("\0", (string) $name);
if (count($name) === 1) {
$name = $name[0];
} elseif ($name[1] !== $className) {
$name = $name[1] . '::' . $name[2];
} else {
$name = $name[2];
}
$properties[$name] = $value;
}
return $properties;
}
}

View File

@ -0,0 +1,68 @@
# ChangeLog
All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
## [6.0.2] - 2024-07-03
### Changed
* This project now uses PHPStan instead of Psalm for static analysis
## [6.0.1] - 2024-06-17
### Changed
* [#30](https://github.com/sebastianbergmann/recursion-context/pull/30): Use more efficient `spl_object_id()` over `spl_object_hash()`
## [6.0.0] - 2024-02-02
### Removed
* This component is no longer supported on PHP 8.1
## [5.0.0] - 2023-02-03
### Removed
* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0
## [4.0.5] - 2023-02-03
### Fixed
* [#26](https://github.com/sebastianbergmann/recursion-context/pull/26): Don't clobber `null` values if `array_key_exists(PHP_INT_MAX, $array)`
## [4.0.4] - 2020-10-26
### Fixed
* `SebastianBergmann\RecursionContext\Exception` now correctly extends `\Throwable`
## [4.0.3] - 2020-09-28
### Changed
* [#21](https://github.com/sebastianbergmann/recursion-context/pull/21): Add type annotations for in/out parameters
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [4.0.2] - 2020-06-26
### Added
* This component is now supported on PHP 8
## [4.0.1] - 2020-06-15
### Changed
* Tests etc. are now ignored for archive exports
[6.0.2]: https://github.com/sebastianbergmann/recursion-context/compare/6.0.1...6.0.2
[6.0.1]: https://github.com/sebastianbergmann/recursion-context/compare/6.0.0...6.0.1
[6.0.0]: https://github.com/sebastianbergmann/recursion-context/compare/5.0...6.0.0
[5.0.0]: https://github.com/sebastianbergmann/recursion-context/compare/4.0.5...5.0.0
[4.0.5]: https://github.com/sebastianbergmann/recursion-context/compare/4.0.4...4.0.5
[4.0.4]: https://github.com/sebastianbergmann/recursion-context/compare/4.0.3...4.0.4
[4.0.3]: https://github.com/sebastianbergmann/recursion-context/compare/4.0.2...4.0.3
[4.0.2]: https://github.com/sebastianbergmann/recursion-context/compare/4.0.1...4.0.2
[4.0.1]: https://github.com/sebastianbergmann/recursion-context/compare/4.0.0...4.0.1

View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2002-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,19 @@
[![Latest Stable Version](https://poser.pugx.org/sebastian/recursion-context/v/stable.png)](https://packagist.org/packages/sebastian/recursion-context)
[![CI Status](https://github.com/sebastianbergmann/recursion-context/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/recursion-context/actions)
[![codecov](https://codecov.io/gh/sebastianbergmann/recursion-context/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/recursion-context)
# sebastian/recursion-context
## Installation
You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/):
```
composer require sebastian/recursion-context
```
If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:
```
composer require --dev sebastian/recursion-context
```

View File

@ -0,0 +1,30 @@
# Security Policy
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email `sebastian@phpunit.de`.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
## Web Context
The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit.
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.

View File

@ -0,0 +1,48 @@
{
"name": "sebastian/recursion-context",
"description": "Provides functionality to recursively process PHP variables",
"homepage": "https://github.com/sebastianbergmann/recursion-context",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
}
],
"support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
"security": "https://github.com/sebastianbergmann/recursion-context/security/policy"
},
"prefer-stable": true,
"config": {
"platform": {
"php": "8.2.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"require": {
"php": ">=8.2"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
},
"autoload": {
"classmap": [
"src/"
]
},
"extra": {
"branch-alias": {
"dev-main": "6.0-dev"
}
}
}

View File

@ -0,0 +1,154 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/recursion-context.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\RecursionContext;
use const PHP_INT_MAX;
use const PHP_INT_MIN;
use function array_key_exists;
use function array_pop;
use function array_slice;
use function count;
use function is_array;
use function random_int;
use function spl_object_id;
use SplObjectStorage;
final class Context
{
/**
* @var list<array<mixed>>
*/
private array $arrays = [];
/**
* @var SplObjectStorage<object, null>
*/
private SplObjectStorage $objects;
public function __construct()
{
$this->objects = new SplObjectStorage;
}
/**
* @codeCoverageIgnore
*/
public function __destruct()
{
foreach ($this->arrays as &$array) {
if (is_array($array)) {
array_pop($array);
array_pop($array);
}
}
}
/**
* @template T of object|array
*
* @param T $value
*
* @param-out T $value
*/
public function add(array|object &$value): false|int|string
{
if (is_array($value)) {
return $this->addArray($value);
}
return $this->addObject($value);
}
/**
* @template T of object|array
*
* @param T $value
*
* @param-out T $value
*/
public function contains(array|object &$value): false|int|string
{
if (is_array($value)) {
return $this->containsArray($value);
}
return $this->containsObject($value);
}
/**
* @param array<mixed> $array
*/
private function addArray(array &$array): int
{
$key = $this->containsArray($array);
if ($key !== false) {
return $key;
}
$key = count($this->arrays);
$this->arrays[] = &$array;
if (!array_key_exists(PHP_INT_MAX, $array) && !array_key_exists(PHP_INT_MAX - 1, $array)) {
$array[] = $key;
$array[] = $this->objects;
} else {
/* Cover the improbable case, too.
*
* Note that array_slice() (used in containsArray()) will return the
* last two values added, *not necessarily* the highest integer keys
* in the array. Therefore, the order of these writes to $array is
* important, but the actual keys used is not. */
do {
/** @noinspection PhpUnhandledExceptionInspection */
$key = random_int(PHP_INT_MIN, PHP_INT_MAX);
} while (array_key_exists($key, $array));
$array[$key] = $key;
do {
/** @noinspection PhpUnhandledExceptionInspection */
$key = random_int(PHP_INT_MIN, PHP_INT_MAX);
} while (array_key_exists($key, $array));
$array[$key] = $this->objects;
}
return $key;
}
private function addObject(object $object): int
{
if (!$this->objects->contains($object)) {
$this->objects->attach($object);
}
return spl_object_id($object);
}
/**
* @param array<mixed> $array
*/
private function containsArray(array $array): false|int
{
$end = array_slice($array, -2);
return isset($end[1]) && $end[1] === $this->objects ? $end[0] : false;
}
private function containsObject(object $value): false|int
{
if ($this->objects->contains($value)) {
return spl_object_id($value);
}
return false;
}
}

190
vendor/sebastian/type/ChangeLog.md vendored Normal file
View File

@ -0,0 +1,190 @@
# ChangeLog
All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [5.0.1] - 2024-07-03
### Changed
* This project now uses PHPStan instead of Psalm for static analysis
## [5.0.0] - 2024-02-02
### Removed
* This component is no longer supported on PHP 8.1
## [4.0.0] - 2023-02-03
### Removed
* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0
## [3.2.1] - 2023-02-03
### Fixed
* [#28](https://github.com/sebastianbergmann/type/pull/28): Potential undefined offset warning/notice
## [3.2.0] - 2022-09-12
### Added
* [#25](https://github.com/sebastianbergmann/type/issues/25): Support Disjunctive Normal Form types
* Added `ReflectionMapper::fromParameterTypes()`
* Added `IntersectionType::types()` and `UnionType::types()`
* Added `UnionType::containsIntersectionTypes()`
## [3.1.0] - 2022-08-29
### Added
* [#21](https://github.com/sebastianbergmann/type/issues/21): Support `true` as stand-alone type
## [3.0.0] - 2022-03-15
### Added
* Support for intersection types introduced in PHP 8.1
* Support for the `never` return type introduced in PHP 8.1
* Added `Type::isCallable()`, `Type::isGenericObject()`, `Type::isIterable()`, `Type::isMixed()`, `Type::isNever()`, `Type::isNull()`, `Type::isObject()`, `Type::isSimple()`, `Type::isStatic()`, `Type::isUnion()`, `Type::isUnknown()`, and `Type::isVoid()`
### Changed
* Renamed `ReflectionMapper::fromMethodReturnType(ReflectionMethod $method)` to `ReflectionMapper::fromReturnType(ReflectionFunctionAbstract $functionOrMethod)`
### Removed
* Removed `Type::getReturnTypeDeclaration()` (use `Type::asString()` instead and prefix its result with `': '`)
* Removed `TypeName::getNamespaceName()` (use `TypeName::namespaceName()` instead)
* Removed `TypeName::getSimpleName()` (use `TypeName::simpleName()` instead)
* Removed `TypeName::getQualifiedName()` (use `TypeName::qualifiedName()` instead)
## [2.3.4] - 2021-06-15
### Fixed
* Fixed regression introduced in 2.3.3
## [2.3.3] - 2021-06-15 [YANKED]
### Fixed
* [#15](https://github.com/sebastianbergmann/type/issues/15): "false" pseudo type is not handled properly
## [2.3.2] - 2021-06-04
### Fixed
* Fixed handling of tentatively declared return types
## [2.3.1] - 2020-10-26
### Fixed
* `SebastianBergmann\Type\Exception` now correctly extends `\Throwable`
## [2.3.0] - 2020-10-06
### Added
* [#14](https://github.com/sebastianbergmann/type/issues/14): Support for `static` return type that is introduced in PHP 8
## [2.2.2] - 2020-09-28
### Changed
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [2.2.1] - 2020-07-05
### Fixed
* Fixed handling of `mixed` type in `ReflectionMapper::fromMethodReturnType()`
## [2.2.0] - 2020-07-05
### Added
* Added `MixedType` object for representing PHP 8's `mixed` type
## [2.1.1] - 2020-06-26
### Added
* This component is now supported on PHP 8
## [2.1.0] - 2020-06-01
### Added
* Added `UnionType` object for representing PHP 8's Union Types
* Added `ReflectionMapper::fromMethodReturnType()` for mapping `\ReflectionMethod::getReturnType()` to a `Type` object
* Added `Type::name()` for retrieving the name of a type
* Added `Type::asString()` for retrieving a textual representation of a type
### Changed
* Deprecated `Type::getReturnTypeDeclaration()` (use `Type::asString()` instead and prefix its result with `': '`)
* Deprecated `TypeName::getNamespaceName()` (use `TypeName::namespaceName()` instead)
* Deprecated `TypeName::getSimpleName()` (use `TypeName::simpleName()` instead)
* Deprecated `TypeName::getQualifiedName()` (use `TypeName::qualifiedName()` instead)
## [2.0.0] - 2020-02-07
### Removed
* This component is no longer supported on PHP 7.2
## [1.1.3] - 2019-07-02
### Fixed
* Fixed class name comparison in `ObjectType` to be case-insensitive
## [1.1.2] - 2019-06-19
### Fixed
* Fixed handling of `object` type
## [1.1.1] - 2019-06-08
### Fixed
* Fixed autoloading of `callback_function.php` fixture file
## [1.1.0] - 2019-06-07
### Added
* Added support for `callable` type
* Added support for `iterable` type
## [1.0.0] - 2019-06-06
* Initial release based on [code contributed by Michel Hartmann to PHPUnit](https://github.com/sebastianbergmann/phpunit/pull/3673)
[5.0.1]: https://github.com/sebastianbergmann/type/compare/5.0.0...5.0.1
[5.0.0]: https://github.com/sebastianbergmann/type/compare/4.0...5.0.0
[4.0.0]: https://github.com/sebastianbergmann/type/compare/3.2.1...4.0.0
[3.2.1]: https://github.com/sebastianbergmann/type/compare/3.2.0...3.2.1
[3.2.0]: https://github.com/sebastianbergmann/type/compare/3.1.0...3.2.0
[3.1.0]: https://github.com/sebastianbergmann/type/compare/3.0.0...3.1.0
[3.0.0]: https://github.com/sebastianbergmann/type/compare/2.3.4...3.0.0
[2.3.4]: https://github.com/sebastianbergmann/type/compare/ca39369c41313ed12c071ed38ecda8fcdb248859...2.3.4
[2.3.3]: https://github.com/sebastianbergmann/type/compare/2.3.2...ca39369c41313ed12c071ed38ecda8fcdb248859
[2.3.2]: https://github.com/sebastianbergmann/type/compare/2.3.1...2.3.2
[2.3.1]: https://github.com/sebastianbergmann/type/compare/2.3.0...2.3.1
[2.3.0]: https://github.com/sebastianbergmann/type/compare/2.2.2...2.3.0
[2.2.2]: https://github.com/sebastianbergmann/type/compare/2.2.1...2.2.2
[2.2.1]: https://github.com/sebastianbergmann/type/compare/2.2.0...2.2.1
[2.2.0]: https://github.com/sebastianbergmann/type/compare/2.1.1...2.2.0
[2.1.1]: https://github.com/sebastianbergmann/type/compare/2.1.0...2.1.1
[2.1.0]: https://github.com/sebastianbergmann/type/compare/2.0.0...2.1.0
[2.0.0]: https://github.com/sebastianbergmann/type/compare/1.1.3...2.0.0
[1.1.3]: https://github.com/sebastianbergmann/type/compare/1.1.2...1.1.3
[1.1.2]: https://github.com/sebastianbergmann/type/compare/1.1.1...1.1.2
[1.1.1]: https://github.com/sebastianbergmann/type/compare/1.1.0...1.1.1
[1.1.0]: https://github.com/sebastianbergmann/type/compare/1.0.0...1.1.0
[1.0.0]: https://github.com/sebastianbergmann/type/compare/ff74aa41746bd8d10e931843ebf37d42da513ede...1.0.0

29
vendor/sebastian/type/LICENSE vendored Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2019-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21
vendor/sebastian/type/README.md vendored Normal file
View File

@ -0,0 +1,21 @@
[![Latest Stable Version](https://poser.pugx.org/sebastian/type/v/stable.png)](https://packagist.org/packages/sebastian/type)
[![CI Status](https://github.com/sebastianbergmann/type/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/type/actions)
[![codecov](https://codecov.io/gh/sebastianbergmann/type/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/type)
# sebastian/type
Collection of value objects that represent the types of the PHP type system.
## Installation
You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/):
```
composer require sebastian/type
```
If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:
```
composer require --dev sebastian/type
```

30
vendor/sebastian/type/SECURITY.md vendored Normal file
View File

@ -0,0 +1,30 @@
# Security Policy
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email `sebastian@phpunit.de`.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
## Web Context
The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit.
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.

51
vendor/sebastian/type/composer.json vendored Normal file
View File

@ -0,0 +1,51 @@
{
"name": "sebastian/type",
"description": "Collection of value objects that represent the types of the PHP type system",
"type": "library",
"homepage": "https://github.com/sebastianbergmann/type",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"support": {
"issues": "https://github.com/sebastianbergmann/type/issues",
"security": "https://github.com/sebastianbergmann/type/security/policy"
},
"prefer-stable": true,
"require": {
"php": ">=8.2"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
},
"config": {
"platform": {
"php": "8.2.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"autoload": {
"classmap": [
"src/"
]
},
"autoload-dev": {
"classmap": [
"tests/_fixture"
],
"files": [
"tests/_fixture/callback_function.php",
"tests/_fixture/functions_that_declare_return_types.php"
]
},
"extra": {
"branch-alias": {
"dev-main": "5.0-dev"
}
}
}

12
vendor/sebastian/type/infection.json vendored Normal file
View File

@ -0,0 +1,12 @@
{
"source": {
"directories": [
"src"
]
},
"mutators": {
"@default": true
},
"minMsi": 100,
"minCoveredMsi": 100
}

41
vendor/sebastian/type/src/Parameter.php vendored Normal file
View File

@ -0,0 +1,41 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
final readonly class Parameter
{
/**
* @var non-empty-string
*/
private string $name;
private Type $type;
/**
* @param non-empty-string $name
*/
public function __construct(string $name, Type $type)
{
$this->name = $name;
$this->type = $type;
}
/**
* @return non-empty-string
*/
public function name(): string
{
return $this->name;
}
public function type(): Type
{
return $this->type;
}
}

View File

@ -0,0 +1,174 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
use function assert;
use ReflectionFunction;
use ReflectionIntersectionType;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionType;
use ReflectionUnionType;
final class ReflectionMapper
{
/**
* @return list<Parameter>
*/
public function fromParameterTypes(ReflectionFunction|ReflectionMethod $functionOrMethod): array
{
$parameters = [];
foreach ($functionOrMethod->getParameters() as $parameter) {
$name = $parameter->getName();
assert($name !== '');
if (!$parameter->hasType()) {
$parameters[] = new Parameter($name, new UnknownType);
continue;
}
$type = $parameter->getType();
if ($type instanceof ReflectionNamedType) {
$parameters[] = new Parameter(
$name,
$this->mapNamedType($type, $functionOrMethod),
);
continue;
}
if ($type instanceof ReflectionUnionType) {
$parameters[] = new Parameter(
$name,
$this->mapUnionType($type, $functionOrMethod),
);
continue;
}
if ($type instanceof ReflectionIntersectionType) {
$parameters[] = new Parameter(
$name,
$this->mapIntersectionType($type, $functionOrMethod),
);
}
}
return $parameters;
}
public function fromReturnType(ReflectionFunction|ReflectionMethod $functionOrMethod): Type
{
if (!$this->hasReturnType($functionOrMethod)) {
return new UnknownType;
}
$returnType = $this->returnType($functionOrMethod);
assert($returnType instanceof ReflectionNamedType || $returnType instanceof ReflectionUnionType || $returnType instanceof ReflectionIntersectionType);
if ($returnType instanceof ReflectionNamedType) {
return $this->mapNamedType($returnType, $functionOrMethod);
}
if ($returnType instanceof ReflectionUnionType) {
return $this->mapUnionType($returnType, $functionOrMethod);
}
if ($returnType instanceof ReflectionIntersectionType) {
return $this->mapIntersectionType($returnType, $functionOrMethod);
}
}
private function mapNamedType(ReflectionNamedType $type, ReflectionFunction|ReflectionMethod $functionOrMethod): Type
{
if ($functionOrMethod instanceof ReflectionMethod && $type->getName() === 'self') {
return ObjectType::fromName(
$functionOrMethod->getDeclaringClass()->getName(),
$type->allowsNull(),
);
}
if ($functionOrMethod instanceof ReflectionMethod && $type->getName() === 'static') {
return new StaticType(
TypeName::fromReflection($functionOrMethod->getDeclaringClass()),
$type->allowsNull(),
);
}
if ($type->getName() === 'mixed') {
return new MixedType;
}
if ($functionOrMethod instanceof ReflectionMethod && $type->getName() === 'parent') {
return ObjectType::fromName(
$functionOrMethod->getDeclaringClass()->getParentClass()->getName(),
$type->allowsNull(),
);
}
return Type::fromName(
$type->getName(),
$type->allowsNull(),
);
}
private function mapUnionType(ReflectionUnionType $type, ReflectionFunction|ReflectionMethod $functionOrMethod): Type
{
$types = [];
foreach ($type->getTypes() as $_type) {
if ($_type instanceof ReflectionNamedType) {
$types[] = $this->mapNamedType($_type, $functionOrMethod);
continue;
}
$types[] = $this->mapIntersectionType($_type, $functionOrMethod);
}
return new UnionType(...$types);
}
private function mapIntersectionType(ReflectionIntersectionType $type, ReflectionFunction|ReflectionMethod $functionOrMethod): Type
{
$types = [];
foreach ($type->getTypes() as $_type) {
assert($_type instanceof ReflectionNamedType);
$types[] = $this->mapNamedType($_type, $functionOrMethod);
}
return new IntersectionType(...$types);
}
private function hasReturnType(ReflectionFunction|ReflectionMethod $functionOrMethod): bool
{
if ($functionOrMethod->hasReturnType()) {
return true;
}
return $functionOrMethod->hasTentativeReturnType();
}
private function returnType(ReflectionFunction|ReflectionMethod $functionOrMethod): ?ReflectionType
{
if ($functionOrMethod->hasReturnType()) {
return $functionOrMethod->getReturnType();
}
return $functionOrMethod->getTentativeReturnType();
}
}

98
vendor/sebastian/type/src/TypeName.php vendored Normal file
View File

@ -0,0 +1,98 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
use function array_pop;
use function assert;
use function explode;
use function implode;
use function substr;
use ReflectionClass;
final readonly class TypeName
{
private ?string $namespaceName;
/**
* @var non-empty-string
*/
private string $simpleName;
/**
* @param class-string $fullClassName
*/
public static function fromQualifiedName(string $fullClassName): self
{
if ($fullClassName[0] === '\\') {
$fullClassName = substr($fullClassName, 1);
}
$classNameParts = explode('\\', $fullClassName);
$simpleName = array_pop($classNameParts);
$namespaceName = implode('\\', $classNameParts);
assert($simpleName !== '');
return new self($namespaceName, $simpleName);
}
/**
* @phpstan-ignore missingType.generics
*/
public static function fromReflection(ReflectionClass $type): self
{
return new self(
$type->getNamespaceName(),
$type->getShortName(),
);
}
/**
* @param non-empty-string $simpleName
*/
public function __construct(?string $namespaceName, string $simpleName)
{
if ($namespaceName === '') {
$namespaceName = null;
}
$this->namespaceName = $namespaceName;
$this->simpleName = $simpleName;
}
public function namespaceName(): ?string
{
return $this->namespaceName;
}
/**
* @return non-empty-string
*/
public function simpleName(): string
{
return $this->simpleName;
}
/**
* @return non-empty-string
*/
public function qualifiedName(): string
{
return $this->namespaceName === null
? $this->simpleName
: $this->namespaceName . '\\' . $this->simpleName;
}
public function isNamespaced(): bool
{
return $this->namespaceName !== null;
}
}

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
use Throwable;
interface Exception extends Throwable
{
}

View File

@ -0,0 +1,14 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
final class RuntimeException extends \RuntimeException implements Exception
{
}

View File

@ -0,0 +1,185 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
use function assert;
use function class_exists;
use function count;
use function explode;
use function function_exists;
use function is_array;
use function is_object;
use function is_string;
use function str_contains;
use Closure;
use ReflectionClass;
use ReflectionObject;
final class CallableType extends Type
{
private bool $allowsNull;
public function __construct(bool $nullable)
{
$this->allowsNull = $nullable;
}
public function isAssignable(Type $other): bool
{
if ($this->allowsNull && $other instanceof NullType) {
return true;
}
if ($other instanceof self) {
return true;
}
if ($other instanceof ObjectType) {
if ($this->isClosure($other)) {
return true;
}
if ($this->hasInvokeMethod($other)) {
return true;
}
}
if ($other instanceof SimpleType) {
if ($this->isFunction($other)) {
return true;
}
if ($this->isClassCallback($other)) {
return true;
}
if ($this->isObjectCallback($other)) {
return true;
}
}
return false;
}
/**
* @return 'callable'
*/
public function name(): string
{
return 'callable';
}
public function allowsNull(): bool
{
return $this->allowsNull;
}
public function isCallable(): bool
{
return true;
}
private function isClosure(ObjectType $type): bool
{
return $type->className()->qualifiedName() === Closure::class;
}
private function hasInvokeMethod(ObjectType $type): bool
{
$className = $type->className()->qualifiedName();
assert(class_exists($className));
return (new ReflectionClass($className))->hasMethod('__invoke');
}
private function isFunction(SimpleType $type): bool
{
if (!is_string($type->value())) {
return false;
}
return function_exists($type->value());
}
private function isObjectCallback(SimpleType $type): bool
{
if (!is_array($type->value())) {
return false;
}
if (count($type->value()) !== 2) {
return false;
}
if (!isset($type->value()[0], $type->value()[1])) {
return false;
}
if (!is_object($type->value()[0]) || !is_string($type->value()[1])) {
return false;
}
[$object, $methodName] = $type->value();
return (new ReflectionObject($object))->hasMethod($methodName);
}
private function isClassCallback(SimpleType $type): bool
{
if (!is_string($type->value()) && !is_array($type->value())) {
return false;
}
if (is_string($type->value())) {
if (!str_contains($type->value(), '::')) {
return false;
}
[$className, $methodName] = explode('::', $type->value());
}
if (is_array($type->value())) {
if (count($type->value()) !== 2) {
return false;
}
if (!isset($type->value()[0], $type->value()[1])) {
return false;
}
if (!is_string($type->value()[0]) || !is_string($type->value()[1])) {
return false;
}
[$className, $methodName] = $type->value();
}
/** @phpstan-ignore isset.variable */
assert(isset($className));
/** @phpstan-ignore isset.variable */
assert(isset($methodName));
if (!class_exists($className)) {
return false;
}
$class = new ReflectionClass($className);
if (!$class->hasMethod($methodName)) {
return false;
}
$method = $class->getMethod($methodName);
return $method->isPublic() && $method->isStatic();
}
}

View File

@ -0,0 +1,42 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
final class FalseType extends Type
{
public function isAssignable(Type $other): bool
{
if ($other instanceof self) {
return true;
}
return $other instanceof SimpleType &&
$other->name() === 'bool' &&
$other->value() === false;
}
/**
* @return 'false'
*/
public function name(): string
{
return 'false';
}
public function allowsNull(): bool
{
return false;
}
public function isFalse(): bool
{
return true;
}
}

View File

@ -0,0 +1,51 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
final class GenericObjectType extends Type
{
private bool $allowsNull;
public function __construct(bool $nullable)
{
$this->allowsNull = $nullable;
}
public function isAssignable(Type $other): bool
{
if ($this->allowsNull && $other instanceof NullType) {
return true;
}
if (!$other instanceof ObjectType) {
return false;
}
return true;
}
/**
* @return 'object'
*/
public function name(): string
{
return 'object';
}
public function allowsNull(): bool
{
return $this->allowsNull;
}
public function isGenericObject(): bool
{
return true;
}
}

View File

@ -0,0 +1,132 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
use function array_is_list;
use function assert;
use function count;
use function implode;
use function in_array;
use function sort;
final class IntersectionType extends Type
{
/**
* @var non-empty-list<Type>
*/
private array $types;
/**
* @throws RuntimeException
*/
public function __construct(Type ...$types)
{
$this->ensureMinimumOfTwoTypes(...$types);
$this->ensureOnlyValidTypes(...$types);
$this->ensureNoDuplicateTypes(...$types);
assert(array_is_list($types) && !empty($types));
$this->types = $types;
}
public function isAssignable(Type $other): bool
{
return $other->isObject();
}
/**
* @return non-empty-string
*/
public function asString(): string
{
return $this->name();
}
/**
* @return non-empty-string
*/
public function name(): string
{
$types = [];
foreach ($this->types as $type) {
$types[] = $type->name();
}
sort($types);
return implode('&', $types);
}
public function allowsNull(): bool
{
return false;
}
public function isIntersection(): bool
{
return true;
}
/**
* @return non-empty-list<Type>
*/
public function types(): array
{
return $this->types;
}
/**
* @throws RuntimeException
*/
private function ensureMinimumOfTwoTypes(Type ...$types): void
{
if (count($types) < 2) {
throw new RuntimeException(
'An intersection type must be composed of at least two types',
);
}
}
/**
* @throws RuntimeException
*/
private function ensureOnlyValidTypes(Type ...$types): void
{
foreach ($types as $type) {
if (!$type->isObject()) {
throw new RuntimeException(
'An intersection type can only be composed of interfaces and classes',
);
}
}
}
/**
* @throws RuntimeException
*/
private function ensureNoDuplicateTypes(Type ...$types): void
{
$names = [];
foreach ($types as $type) {
assert($type instanceof ObjectType);
$classQualifiedName = $type->className()->qualifiedName();
if (in_array($classQualifiedName, $names, true)) {
throw new RuntimeException('An intersection type must not contain duplicate types');
}
$names[] = $classQualifiedName;
}
}
}

View File

@ -0,0 +1,71 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
use function assert;
use function class_exists;
use function is_iterable;
use ReflectionClass;
final class IterableType extends Type
{
private bool $allowsNull;
public function __construct(bool $nullable)
{
$this->allowsNull = $nullable;
}
/**
* @throws RuntimeException
*/
public function isAssignable(Type $other): bool
{
if ($this->allowsNull && $other instanceof NullType) {
return true;
}
if ($other instanceof self) {
return true;
}
if ($other instanceof SimpleType) {
return is_iterable($other->value());
}
if ($other instanceof ObjectType) {
$className = $other->className()->qualifiedName();
assert(class_exists($className));
return (new ReflectionClass($className))->isIterable();
}
return false;
}
/**
* @return 'iterable'
*/
public function name(): string
{
return 'iterable';
}
public function allowsNull(): bool
{
return $this->allowsNull;
}
public function isIterable(): bool
{
return true;
}
}

View File

@ -0,0 +1,44 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
final class MixedType extends Type
{
public function isAssignable(Type $other): bool
{
return !$other instanceof VoidType;
}
/**
* @return 'mixed'
*/
public function asString(): string
{
return 'mixed';
}
/**
* @return 'mixed'
*/
public function name(): string
{
return 'mixed';
}
public function allowsNull(): bool
{
return true;
}
public function isMixed(): bool
{
return true;
}
}

View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
final class NeverType extends Type
{
public function isAssignable(Type $other): bool
{
return $other instanceof self;
}
/**
* @return 'never'
*/
public function name(): string
{
return 'never';
}
public function allowsNull(): bool
{
return false;
}
public function isNever(): bool
{
return true;
}
}

View File

@ -0,0 +1,44 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
final class NullType extends Type
{
public function isAssignable(Type $other): bool
{
return !($other instanceof VoidType);
}
/**
* @return 'null'
*/
public function name(): string
{
return 'null';
}
/**
* @return 'null'
*/
public function asString(): string
{
return 'null';
}
public function allowsNull(): bool
{
return true;
}
public function isNull(): bool
{
return true;
}
}

View File

@ -0,0 +1,67 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
use function is_subclass_of;
use function strcasecmp;
final class ObjectType extends Type
{
private TypeName $className;
private bool $allowsNull;
public function __construct(TypeName $className, bool $allowsNull)
{
$this->className = $className;
$this->allowsNull = $allowsNull;
}
public function isAssignable(Type $other): bool
{
if ($this->allowsNull && $other instanceof NullType) {
return true;
}
if ($other instanceof self) {
if (0 === strcasecmp($this->className->qualifiedName(), $other->className->qualifiedName())) {
return true;
}
if (is_subclass_of($other->className->qualifiedName(), $this->className->qualifiedName(), true)) {
return true;
}
}
return false;
}
/**
* @return non-empty-string
*/
public function name(): string
{
return $this->className->qualifiedName();
}
public function allowsNull(): bool
{
return $this->allowsNull;
}
public function className(): TypeName
{
return $this->className;
}
public function isObject(): bool
{
return true;
}
}

View File

@ -0,0 +1,94 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
use function strtolower;
final class SimpleType extends Type
{
/**
* @var non-empty-string
*/
private string $name;
private bool $allowsNull;
private mixed $value;
/**
* @param non-empty-string $name
*/
public function __construct(string $name, bool $nullable, mixed $value = null)
{
$this->name = $this->normalize($name);
$this->allowsNull = $nullable;
$this->value = $value;
}
public function isAssignable(Type $other): bool
{
if ($this->allowsNull && $other instanceof NullType) {
return true;
}
if ($this->name === 'bool' && $other->name() === 'true') {
return true;
}
if ($this->name === 'bool' && $other->name() === 'false') {
return true;
}
if ($other instanceof self) {
return $this->name === $other->name;
}
return false;
}
/**
* @return non-empty-string
*/
public function name(): string
{
return $this->name;
}
public function allowsNull(): bool
{
return $this->allowsNull;
}
public function value(): mixed
{
return $this->value;
}
public function isSimple(): bool
{
return true;
}
/**
* @param non-empty-string $name
*
* @return non-empty-string
*/
private function normalize(string $name): string
{
$name = strtolower($name);
return match ($name) {
'boolean' => 'bool',
'real', 'double' => 'float',
'integer' => 'int',
'[]' => 'array',
default => $name,
};
}
}

View File

@ -0,0 +1,64 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
use function is_subclass_of;
use function strcasecmp;
final class StaticType extends Type
{
private TypeName $className;
private bool $allowsNull;
public function __construct(TypeName $className, bool $allowsNull)
{
$this->className = $className;
$this->allowsNull = $allowsNull;
}
public function isAssignable(Type $other): bool
{
if ($this->allowsNull && $other instanceof NullType) {
return true;
}
if (!$other instanceof ObjectType) {
return false;
}
if (0 === strcasecmp($this->className->qualifiedName(), $other->className()->qualifiedName())) {
return true;
}
if (is_subclass_of($other->className()->qualifiedName(), $this->className->qualifiedName(), true)) {
return true;
}
return false;
}
/**
* @return 'static'
*/
public function name(): string
{
return 'static';
}
public function allowsNull(): bool
{
return $this->allowsNull;
}
public function isStatic(): bool
{
return true;
}
}

View File

@ -0,0 +1,42 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
final class TrueType extends Type
{
public function isAssignable(Type $other): bool
{
if ($other instanceof self) {
return true;
}
return $other instanceof SimpleType &&
$other->name() === 'bool' &&
$other->value() === true;
}
/**
* @return 'true'
*/
public function name(): string
{
return 'true';
}
public function allowsNull(): bool
{
return false;
}
public function isTrue(): bool
{
return true;
}
}

198
vendor/sebastian/type/src/type/Type.php vendored Normal file
View File

@ -0,0 +1,198 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
use function gettype;
use function strtolower;
abstract class Type
{
public static function fromValue(mixed $value, bool $allowsNull): self
{
if ($allowsNull === false) {
if ($value === true) {
return new TrueType;
}
if ($value === false) {
return new FalseType;
}
}
$typeName = gettype($value);
if ($typeName === 'object') {
return new ObjectType(TypeName::fromQualifiedName($value::class), $allowsNull);
}
$type = self::fromName($typeName, $allowsNull);
if ($type instanceof SimpleType) {
$type = new SimpleType($typeName, $allowsNull, $value);
}
return $type;
}
/**
* @param non-empty-string $typeName
*/
public static function fromName(string $typeName, bool $allowsNull): self
{
return match (strtolower($typeName)) {
'callable' => new CallableType($allowsNull),
'true' => new TrueType,
'false' => new FalseType,
'iterable' => new IterableType($allowsNull),
'never' => new NeverType,
'null' => new NullType,
'object' => new GenericObjectType($allowsNull),
'unknown type' => new UnknownType,
'void' => new VoidType,
'array', 'bool', 'boolean', 'double', 'float', 'int', 'integer', 'real', 'resource', 'resource (closed)', 'string' => new SimpleType($typeName, $allowsNull),
'mixed' => new MixedType,
default => new ObjectType(TypeName::fromQualifiedName($typeName), $allowsNull),
};
}
public function asString(): string
{
return ($this->allowsNull() ? '?' : '') . $this->name();
}
/**
* @phpstan-assert-if-true CallableType $this
*/
public function isCallable(): bool
{
return false;
}
/**
* @phpstan-assert-if-true TrueType $this
*/
public function isTrue(): bool
{
return false;
}
/**
* @phpstan-assert-if-true FalseType $this
*/
public function isFalse(): bool
{
return false;
}
/**
* @phpstan-assert-if-true GenericObjectType $this
*/
public function isGenericObject(): bool
{
return false;
}
/**
* @phpstan-assert-if-true IntersectionType $this
*/
public function isIntersection(): bool
{
return false;
}
/**
* @phpstan-assert-if-true IterableType $this
*/
public function isIterable(): bool
{
return false;
}
/**
* @phpstan-assert-if-true MixedType $this
*/
public function isMixed(): bool
{
return false;
}
/**
* @phpstan-assert-if-true NeverType $this
*/
public function isNever(): bool
{
return false;
}
/**
* @phpstan-assert-if-true NullType $this
*/
public function isNull(): bool
{
return false;
}
/**
* @phpstan-assert-if-true ObjectType $this
*/
public function isObject(): bool
{
return false;
}
/**
* @phpstan-assert-if-true SimpleType $this
*/
public function isSimple(): bool
{
return false;
}
/**
* @phpstan-assert-if-true StaticType $this
*/
public function isStatic(): bool
{
return false;
}
/**
* @phpstan-assert-if-true UnionType $this
*/
public function isUnion(): bool
{
return false;
}
/**
* @phpstan-assert-if-true UnknownType $this
*/
public function isUnknown(): bool
{
return false;
}
/**
* @phpstan-assert-if-true VoidType $this
*/
public function isVoid(): bool
{
return false;
}
abstract public function isAssignable(self $other): bool;
/**
* @return non-empty-string
*/
abstract public function name(): string;
abstract public function allowsNull(): bool;
}

View File

@ -0,0 +1,150 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
use function array_is_list;
use function assert;
use function count;
use function implode;
use function sort;
final class UnionType extends Type
{
/**
* @var non-empty-list<Type>
*/
private array $types;
/**
* @throws RuntimeException
*/
public function __construct(Type ...$types)
{
$this->ensureMinimumOfTwoTypes(...$types);
$this->ensureOnlyValidTypes(...$types);
assert(array_is_list($types) && !empty($types));
$this->types = $types;
}
public function isAssignable(Type $other): bool
{
foreach ($this->types as $type) {
if ($type->isAssignable($other)) {
return true;
}
}
return false;
}
/**
* @return non-empty-string
*/
public function asString(): string
{
return $this->name();
}
/**
* @return non-empty-string
*/
public function name(): string
{
$types = [];
foreach ($this->types as $type) {
if ($type->isIntersection()) {
$types[] = '(' . $type->name() . ')';
continue;
}
$types[] = $type->name();
}
sort($types);
$name = implode('|', $types);
/** @phpstan-ignore empty.variable */
assert(!empty($name));
return $name;
}
public function allowsNull(): bool
{
foreach ($this->types as $type) {
if ($type instanceof NullType) {
return true;
}
}
return false;
}
public function isUnion(): bool
{
return true;
}
public function containsIntersectionTypes(): bool
{
foreach ($this->types as $type) {
if ($type->isIntersection()) {
return true;
}
}
return false;
}
/**
* @return non-empty-list<Type>
*/
public function types(): array
{
return $this->types;
}
/**
* @throws RuntimeException
*/
private function ensureMinimumOfTwoTypes(Type ...$types): void
{
if (count($types) < 2) {
throw new RuntimeException(
'A union type must be composed of at least two types',
);
}
}
/**
* @throws RuntimeException
*/
private function ensureOnlyValidTypes(Type ...$types): void
{
foreach ($types as $type) {
if ($type instanceof UnknownType) {
throw new RuntimeException(
'A union type must not be composed of an unknown type',
);
}
if ($type instanceof VoidType) {
throw new RuntimeException(
'A union type must not be composed of a void type',
);
}
}
}
}

View File

@ -0,0 +1,44 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
final class UnknownType extends Type
{
public function isAssignable(Type $other): bool
{
return true;
}
/**
* @return 'unknown type'
*/
public function name(): string
{
return 'unknown type';
}
/**
* @return ''
*/
public function asString(): string
{
return '';
}
public function allowsNull(): bool
{
return true;
}
public function isUnknown(): bool
{
return true;
}
}

View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/type.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Type;
final class VoidType extends Type
{
public function isAssignable(Type $other): bool
{
return $other instanceof self;
}
/**
* @return 'void'
*/
public function name(): string
{
return 'void';
}
public function allowsNull(): bool
{
return false;
}
public function isVoid(): bool
{
return true;
}
}

57
vendor/sebastian/version/ChangeLog.md vendored Normal file
View File

@ -0,0 +1,57 @@
# ChangeLog
All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
## [5.0.1] - 2024-07-03
### Changed
* This project now uses PHPStan instead of Psalm for static analysis
## [5.0.0] - 2024-02-02
### Removed
* This component is no longer supported on PHP 8.1
## [4.0.1] - 2023-02-07
### Fixed
* [#17](https://github.com/sebastianbergmann/version/pull/17): Release archive contains unnecessary assets
## [4.0.0] - 2023-02-03
### Changed
* `Version::getVersion()` has been renamed to `Version::asString()`
### Removed
* This component is no longer supported on PHP 7.3, PHP 7.4, and PHP 8.0
## [3.0.2] - 2020-09-28
### Changed
* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3`
## [3.0.1] - 2020-06-26
### Added
* This component is now supported on PHP 8
## [3.0.0] - 2020-01-21
### Removed
* This component is no longer supported on PHP 7.1 and PHP 7.2
[5.0.1]: https://github.com/sebastianbergmann/version/compare/5.0.0...5.0.1
[5.0.0]: https://github.com/sebastianbergmann/version/compare/4.0...5.0.0
[4.0.1]: https://github.com/sebastianbergmann/version/compare/4.0.0...4.0.1
[4.0.0]: https://github.com/sebastianbergmann/version/compare/3.0.2...4.0.0
[3.0.2]: https://github.com/sebastianbergmann/version/compare/3.0.1...3.0.2
[3.0.1]: https://github.com/sebastianbergmann/version/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/sebastianbergmann/version/compare/2.0.1...3.0.0

29
vendor/sebastian/version/LICENSE vendored Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2013-2024, Sebastian Bergmann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

50
vendor/sebastian/version/README.md vendored Normal file
View File

@ -0,0 +1,50 @@
[![Latest Stable Version](https://poser.pugx.org/sebastian/version/v/stable.png)](https://packagist.org/packages/sebastian/version)
# sebastian/version
**sebastian/version** is a library that helps with managing the version number of Git-hosted PHP projects.
## Installation
You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/):
```
composer require sebastian/version
```
If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:
```
composer require --dev sebastian/version
```
## Usage
The constructor of the `SebastianBergmann\Version` class expects two parameters:
* `$release` is the version number of the latest release (`X.Y.Z`, for instance) or the name of the release series (`X.Y`) when no release has been made from that branch / for that release series yet.
* `$path` is the path to the directory (or a subdirectory thereof) where the sourcecode of the project can be found. Simply passing `__DIR__` here usually suffices.
Apart from the constructor, the `SebastianBergmann\Version` class has a single public method: `asString()`.
Here is a contrived example that shows the basic usage:
```php
<?php declare(strict_types=1);
use SebastianBergmann\Version;
$version = new Version('1.0.0', __DIR__);
var_dump($version->asString());
```
```
string(18) "1.0.0-17-g00f3408"
```
When a new release is prepared, the string that is passed to the constructor as the first argument needs to be updated.
### How SebastianBergmann\Version::asString() works
* If `$path` is not (part of) a Git repository and `$release` is in `X.Y.Z` format then `$release` is returned as-is.
* If `$path` is not (part of) a Git repository and `$release` is in `X.Y` format then `$release` is returned suffixed with `-dev`.
* If `$path` is (part of) a Git repository and `$release` is in `X.Y.Z` format then the output of `git describe --tags` is returned as-is.
* If `$path` is (part of) a Git repository and `$release` is in `X.Y` format then a string is returned that begins with `X.Y` and ends with information from `git describe --tags`.

30
vendor/sebastian/version/SECURITY.md vendored Normal file
View File

@ -0,0 +1,30 @@
# Security Policy
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email `sebastian@phpunit.de`.
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
* The type of issue
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
## Web Context
The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit.
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.

38
vendor/sebastian/version/composer.json vendored Normal file
View File

@ -0,0 +1,38 @@
{
"name": "sebastian/version",
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"support": {
"issues": "https://github.com/sebastianbergmann/version/issues",
"security": "https://github.com/sebastianbergmann/version/security/policy"
},
"config": {
"platform": {
"php": "8.2.0"
},
"optimize-autoloader": true,
"sort-packages": true
},
"prefer-stable": true,
"require": {
"php": ">=8.2"
},
"autoload": {
"classmap": [
"src/"
]
},
"extra": {
"branch-alias": {
"dev-main": "5.0-dev"
}
}
}

112
vendor/sebastian/version/src/Version.php vendored Normal file
View File

@ -0,0 +1,112 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/version.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann;
use function end;
use function explode;
use function fclose;
use function is_dir;
use function is_resource;
use function proc_close;
use function proc_open;
use function stream_get_contents;
use function substr_count;
use function trim;
final readonly class Version
{
/**
* @var non-empty-string
*/
private string $version;
/**
* @param non-empty-string $release
* @param non-empty-string $path
*/
public function __construct(string $release, string $path)
{
$this->version = $this->generate($release, $path);
}
/**
* @return non-empty-string
*/
public function asString(): string
{
return $this->version;
}
/**
* @param non-empty-string $release
* @param non-empty-string $path
*
* @return non-empty-string
*/
private function generate(string $release, string $path): string
{
if (substr_count($release, '.') + 1 === 3) {
$version = $release;
} else {
$version = $release . '-dev';
}
$git = $this->getGitInformation($path);
if (!$git) {
return $version;
}
if (substr_count($release, '.') + 1 === 3) {
return $git;
}
$git = explode('-', $git);
return $release . '-' . end($git);
}
/**
* @param non-empty-string $path
*/
private function getGitInformation(string $path): false|string
{
if (!is_dir($path . DIRECTORY_SEPARATOR . '.git')) {
return false;
}
$process = proc_open(
'git describe --tags',
[
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
],
$pipes,
$path,
);
if (!is_resource($process)) {
return false;
}
$result = trim((string) stream_get_contents($pipes[1]));
fclose($pipes[1]);
fclose($pipes[2]);
$returnCode = proc_close($process);
if ($returnCode !== 0) {
return false;
}
return $result;
}
}