Update website

This commit is contained in:
Guilhem Lavaux 2024-11-19 08:02:04 +01:00
parent 4413528994
commit 1d90fbf296
6865 changed files with 1091082 additions and 0 deletions

View file

@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

View file

@ -0,0 +1,5 @@
CHANGELOG
=========
The changelog is maintained for all Symfony contracts at the following URL:
https://github.com/symfony/contracts/blob/main/CHANGELOG.md

View file

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

View file

@ -0,0 +1,26 @@
Symfony Deprecation Contracts
=============================
A generic function and convention to trigger deprecation notices.
This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices.
By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component,
the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments.
The function requires at least 3 arguments:
- the name of the Composer package that is triggering the deprecation
- the version of the package that introduced the deprecation
- the message of the deprecation
- more arguments can be provided: they will be inserted in the message using `printf()` formatting
Example:
```php
trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin');
```
This will generate the following message:
`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.`
While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty
`function trigger_deprecation() {}` in your application.

View file

@ -0,0 +1,35 @@
{
"name": "symfony/deprecation-contracts",
"type": "library",
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=7.1"
},
"autoload": {
"files": [
"function.php"
]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "2.5-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (!function_exists('trigger_deprecation')) {
/**
* Triggers a silenced deprecation notice.
*
* @param string $package The name of the Composer package that is triggering the deprecation
* @param string $version The version of the package that introduced the deprecation
* @param string $message The message of the deprecation
* @param mixed ...$args Values to insert in the message using printf() formatting
*
* @author Nicolas Grekas <p@tchwork.com>
*/
function trigger_deprecation(string $package, string $version, string $message, ...$args): void
{
@trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
}
}

View file

@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler;
use Psr\Log\AbstractLogger;
/**
* A buffering logger that stacks logs for later.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class BufferingLogger extends AbstractLogger
{
private $logs = [];
public function log($level, $message, array $context = []): void
{
$this->logs[] = [$level, $message, $context];
}
public function cleanLogs(): array
{
$logs = $this->logs;
$this->logs = [];
return $logs;
}
/**
* @return array
*/
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct()
{
foreach ($this->logs as [$level, $message, $context]) {
if (false !== strpos($message, '{')) {
foreach ($context as $key => $val) {
if (null === $val || \is_scalar($val) || (\is_object($val) && \is_callable([$val, '__toString']))) {
$message = str_replace("{{$key}}", $val, $message);
} elseif ($val instanceof \DateTimeInterface) {
$message = str_replace("{{$key}}", $val->format(\DateTime::RFC3339), $message);
} elseif (\is_object($val)) {
$message = str_replace("{{$key}}", '[object '.\get_class($val).']', $message);
} else {
$message = str_replace("{{$key}}", '['.\gettype($val).']', $message);
}
}
}
error_log(sprintf('%s [%s] %s', date(\DateTime::RFC3339), $level, $message));
}
}
}

View file

@ -0,0 +1,24 @@
CHANGELOG
=========
5.4
---
* Make `DebugClassLoader` trigger deprecation notices on missing return types
* Add `SYMFONY_PATCH_TYPE_DECLARATIONS='force=2'` mode to `DebugClassLoader` to turn annotations into native return types
5.2.0
-----
* added the ability to set `HtmlErrorRenderer::$template` to a custom template to render when not in debug mode.
5.1.0
-----
* The `HtmlErrorRenderer` and `SerializerErrorRenderer` add `X-Debug-Exception` and `X-Debug-Exception-File` headers in debug mode.
4.4.0
-----
* added the component
* added `ErrorHandler::call()` method utility to turn any PHP error into `\ErrorException`

40
vendor/symfony/error-handler/Debug.php vendored Normal file
View file

@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler;
/**
* Registers all the debug tools.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Debug
{
public static function enable(): ErrorHandler
{
error_reporting(-1);
if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
ini_set('display_errors', 0);
} elseif (!filter_var(\ini_get('log_errors'), \FILTER_VALIDATE_BOOLEAN) || \ini_get('error_log')) {
// CLI - display errors only if they're not already logged to STDERR
ini_set('display_errors', 1);
}
@ini_set('zend.assertions', 1);
ini_set('assert.active', 1);
ini_set('assert.exception', 1);
DebugClassLoader::enable();
return ErrorHandler::register(new ErrorHandler(new BufferingLogger(), true));
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Error;
class ClassNotFoundError extends \Error
{
/**
* {@inheritdoc}
*/
public function __construct(string $message, \Throwable $previous)
{
parent::__construct($message, $previous->getCode(), $previous->getPrevious());
foreach ([
'file' => $previous->getFile(),
'line' => $previous->getLine(),
'trace' => $previous->getTrace(),
] as $property => $value) {
$refl = new \ReflectionProperty(\Error::class, $property);
$refl->setAccessible(true);
$refl->setValue($this, $value);
}
}
}

View file

@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Error;
class FatalError extends \Error
{
private $error;
/**
* {@inheritdoc}
*
* @param array $error An array as returned by error_get_last()
*/
public function __construct(string $message, int $code, array $error, ?int $traceOffset = null, bool $traceArgs = true, ?array $trace = null)
{
parent::__construct($message, $code);
$this->error = $error;
if (null !== $trace) {
if (!$traceArgs) {
foreach ($trace as &$frame) {
unset($frame['args'], $frame['this'], $frame);
}
}
} elseif (null !== $traceOffset) {
if (\function_exists('xdebug_get_function_stack') && \in_array(\ini_get('xdebug.mode'), ['develop', false], true) && $trace = @xdebug_get_function_stack()) {
if (0 < $traceOffset) {
array_splice($trace, -$traceOffset);
}
foreach ($trace as &$frame) {
if (!isset($frame['type'])) {
// XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
if (isset($frame['class'])) {
$frame['type'] = '::';
}
} elseif ('dynamic' === $frame['type']) {
$frame['type'] = '->';
} elseif ('static' === $frame['type']) {
$frame['type'] = '::';
}
// XDebug also has a different name for the parameters array
if (!$traceArgs) {
unset($frame['params'], $frame['args']);
} elseif (isset($frame['params']) && !isset($frame['args'])) {
$frame['args'] = $frame['params'];
unset($frame['params']);
}
}
unset($frame);
$trace = array_reverse($trace);
} else {
$trace = [];
}
}
foreach ([
'file' => $error['file'],
'line' => $error['line'],
'trace' => $trace,
] as $property => $value) {
if (null !== $value) {
$refl = new \ReflectionProperty(\Error::class, $property);
$refl->setAccessible(true);
$refl->setValue($this, $value);
}
}
}
/**
* {@inheritdoc}
*/
public function getError(): array
{
return $this->error;
}
}

View file

@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Error;
class OutOfMemoryError extends FatalError
{
}

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Error;
class UndefinedFunctionError extends \Error
{
/**
* {@inheritdoc}
*/
public function __construct(string $message, \Throwable $previous)
{
parent::__construct($message, $previous->getCode(), $previous->getPrevious());
foreach ([
'file' => $previous->getFile(),
'line' => $previous->getLine(),
'trace' => $previous->getTrace(),
] as $property => $value) {
$refl = new \ReflectionProperty(\Error::class, $property);
$refl->setAccessible(true);
$refl->setValue($this, $value);
}
}
}

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Error;
class UndefinedMethodError extends \Error
{
/**
* {@inheritdoc}
*/
public function __construct(string $message, \Throwable $previous)
{
parent::__construct($message, $previous->getCode(), $previous->getPrevious());
foreach ([
'file' => $previous->getFile(),
'line' => $previous->getLine(),
'trace' => $previous->getTrace(),
] as $property => $value) {
$refl = new \ReflectionProperty(\Error::class, $property);
$refl->setAccessible(true);
$refl->setValue($this, $value);
}
}
}

View file

@ -0,0 +1,175 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\ErrorEnhancer;
use Composer\Autoload\ClassLoader;
use Symfony\Component\ErrorHandler\DebugClassLoader;
use Symfony\Component\ErrorHandler\Error\ClassNotFoundError;
use Symfony\Component\ErrorHandler\Error\FatalError;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface
{
public function enhance(\Throwable $error): ?\Throwable
{
// Some specific versions of PHP produce a fatal error when extending a not found class.
$message = !$error instanceof FatalError ? $error->getMessage() : $error->getError()['message'];
if (!preg_match('/^(Class|Interface|Trait) [\'"]([^\'"]+)[\'"] not found$/', $message, $matches)) {
return null;
}
$typeName = strtolower($matches[1]);
$fullyQualifiedClassName = $matches[2];
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
$className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
$namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
$message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix);
$tail = ' for another namespace?';
} else {
$className = $fullyQualifiedClassName;
$message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className);
$tail = '?';
}
if ($candidates = $this->getClassCandidates($className)) {
$tail = array_pop($candidates).'"?';
if ($candidates) {
$tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail;
} else {
$tail = ' for "'.$tail;
}
}
$message .= "\nDid you forget a \"use\" statement".$tail;
return new ClassNotFoundError($message, $error);
}
/**
* Tries to guess the full namespace for a given class name.
*
* By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer
* autoloader (that should cover all common cases).
*
* @param string $class A class name (without its namespace)
*
* Returns an array of possible fully qualified class names
*/
private function getClassCandidates(string $class): array
{
if (!\is_array($functions = spl_autoload_functions())) {
return [];
}
// find Symfony and Composer autoloaders
$classes = [];
foreach ($functions as $function) {
if (!\is_array($function)) {
continue;
}
// get class loaders wrapped by DebugClassLoader
if ($function[0] instanceof DebugClassLoader) {
$function = $function[0]->getClassLoader();
if (!\is_array($function)) {
continue;
}
}
if ($function[0] instanceof ClassLoader) {
foreach ($function[0]->getPrefixes() as $prefix => $paths) {
foreach ($paths as $path) {
$classes[] = $this->findClassInPath($path, $class, $prefix);
}
}
foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) {
foreach ($paths as $path) {
$classes[] = $this->findClassInPath($path, $class, $prefix);
}
}
}
}
return array_unique(array_merge([], ...$classes));
}
private function findClassInPath(string $path, string $class, string $prefix): array
{
$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path);
if (!$path || !is_dir($path)) {
return [];
}
$classes = [];
$filename = $class.'.php';
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
$classes[] = $class;
}
}
return $classes;
}
private function convertFileToClass(string $path, string $file, string $prefix): ?string
{
$candidates = [
// namespaced class
$namespacedClass = str_replace([$path.\DIRECTORY_SEPARATOR, '.php', '/'], ['', '', '\\'], $file),
// namespaced class (with target dir)
$prefix.$namespacedClass,
// namespaced class (with target dir and separator)
$prefix.'\\'.$namespacedClass,
// PEAR class
str_replace('\\', '_', $namespacedClass),
// PEAR class (with target dir)
str_replace('\\', '_', $prefix.$namespacedClass),
// PEAR class (with target dir and separator)
str_replace('\\', '_', $prefix.'\\'.$namespacedClass),
];
if ($prefix) {
$candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); });
}
// We cannot use the autoloader here as most of them use require; but if the class
// is not found, the new autoloader call will require the file again leading to a
// "cannot redeclare class" error.
foreach ($candidates as $candidate) {
if ($this->classExists($candidate)) {
return $candidate;
}
}
try {
require_once $file;
} catch (\Throwable $e) {
return null;
}
foreach ($candidates as $candidate) {
if ($this->classExists($candidate)) {
return $candidate;
}
}
return null;
}
private function classExists(string $class): bool
{
return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
}
}

View file

@ -0,0 +1,20 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\ErrorEnhancer;
interface ErrorEnhancerInterface
{
/**
* Returns an \Throwable instance if the class is able to improve the error, null otherwise.
*/
public function enhance(\Throwable $error): ?\Throwable;
}

View file

@ -0,0 +1,87 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\ErrorEnhancer;
use Symfony\Component\ErrorHandler\Error\FatalError;
use Symfony\Component\ErrorHandler\Error\UndefinedFunctionError;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class UndefinedFunctionErrorEnhancer implements ErrorEnhancerInterface
{
/**
* {@inheritdoc}
*/
public function enhance(\Throwable $error): ?\Throwable
{
if ($error instanceof FatalError) {
return null;
}
$message = $error->getMessage();
$messageLen = \strlen($message);
$notFoundSuffix = '()';
$notFoundSuffixLen = \strlen($notFoundSuffix);
if ($notFoundSuffixLen > $messageLen) {
return null;
}
if (0 !== substr_compare($message, $notFoundSuffix, -$notFoundSuffixLen)) {
return null;
}
$prefix = 'Call to undefined function ';
$prefixLen = \strlen($prefix);
if (0 !== strpos($message, $prefix)) {
return null;
}
$fullyQualifiedFunctionName = substr($message, $prefixLen, -$notFoundSuffixLen);
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) {
$functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1);
$namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex);
$message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix);
} else {
$functionName = $fullyQualifiedFunctionName;
$message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName);
}
$candidates = [];
foreach (get_defined_functions() as $type => $definedFunctionNames) {
foreach ($definedFunctionNames as $definedFunctionName) {
if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) {
$definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1);
} else {
$definedFunctionNameBasename = $definedFunctionName;
}
if ($definedFunctionNameBasename === $functionName) {
$candidates[] = '\\'.$definedFunctionName;
}
}
}
if ($candidates) {
sort($candidates);
$last = array_pop($candidates).'"?';
if ($candidates) {
$candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
} else {
$candidates = '"'.$last;
}
$message .= "\nDid you mean to call ".$candidates;
}
return new UndefinedFunctionError($message, $error);
}
}

View file

@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\ErrorEnhancer;
use Symfony\Component\ErrorHandler\Error\FatalError;
use Symfony\Component\ErrorHandler\Error\UndefinedMethodError;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class UndefinedMethodErrorEnhancer implements ErrorEnhancerInterface
{
/**
* {@inheritdoc}
*/
public function enhance(\Throwable $error): ?\Throwable
{
if ($error instanceof FatalError) {
return null;
}
$message = $error->getMessage();
preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $message, $matches);
if (!$matches) {
return null;
}
$className = $matches[1];
$methodName = $matches[2];
$message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className);
if ('' === $methodName || !class_exists($className) || null === $methods = get_class_methods($className)) {
// failed to get the class or its methods on which an unknown method was called (for example on an anonymous class)
return new UndefinedMethodError($message, $error);
}
$candidates = [];
foreach ($methods as $definedMethodName) {
$lev = levenshtein($methodName, $definedMethodName);
if ($lev <= \strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) {
$candidates[] = $definedMethodName;
}
}
if ($candidates) {
sort($candidates);
$last = array_pop($candidates).'"?';
if ($candidates) {
$candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
} else {
$candidates = '"'.$last;
}
$message .= "\nDid you mean to call ".$candidates;
}
return new UndefinedMethodError($message, $error);
}
}

View file

@ -0,0 +1,813 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Component\ErrorHandler\Error\FatalError;
use Symfony\Component\ErrorHandler\Error\OutOfMemoryError;
use Symfony\Component\ErrorHandler\ErrorEnhancer\ClassNotFoundErrorEnhancer;
use Symfony\Component\ErrorHandler\ErrorEnhancer\ErrorEnhancerInterface;
use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedFunctionErrorEnhancer;
use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedMethodErrorEnhancer;
use Symfony\Component\ErrorHandler\ErrorRenderer\CliErrorRenderer;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
/**
* A generic ErrorHandler for the PHP engine.
*
* Provides five bit fields that control how errors are handled:
* - thrownErrors: errors thrown as \ErrorException
* - loggedErrors: logged errors, when not @-silenced
* - scopedErrors: errors thrown or logged with their local context
* - tracedErrors: errors logged with their stack trace
* - screamedErrors: never @-silenced errors
*
* Each error level can be logged by a dedicated PSR-3 logger object.
* Screaming only applies to logging.
* Throwing takes precedence over logging.
* Uncaught exceptions are logged as E_ERROR.
* E_DEPRECATED and E_USER_DEPRECATED levels never throw.
* E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw.
* Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so.
* As errors have a performance cost, repeated errors are all logged, so that the developer
* can see them and weight them as more important to fix than others of the same level.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @final
*/
class ErrorHandler
{
private $levels = [
\E_DEPRECATED => 'Deprecated',
\E_USER_DEPRECATED => 'User Deprecated',
\E_NOTICE => 'Notice',
\E_USER_NOTICE => 'User Notice',
\E_WARNING => 'Warning',
\E_USER_WARNING => 'User Warning',
\E_COMPILE_WARNING => 'Compile Warning',
\E_CORE_WARNING => 'Core Warning',
\E_USER_ERROR => 'User Error',
\E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
\E_COMPILE_ERROR => 'Compile Error',
\E_PARSE => 'Parse Error',
\E_ERROR => 'Error',
\E_CORE_ERROR => 'Core Error',
];
private $loggers = [
\E_DEPRECATED => [null, LogLevel::INFO],
\E_USER_DEPRECATED => [null, LogLevel::INFO],
\E_NOTICE => [null, LogLevel::WARNING],
\E_USER_NOTICE => [null, LogLevel::WARNING],
\E_WARNING => [null, LogLevel::WARNING],
\E_USER_WARNING => [null, LogLevel::WARNING],
\E_COMPILE_WARNING => [null, LogLevel::WARNING],
\E_CORE_WARNING => [null, LogLevel::WARNING],
\E_USER_ERROR => [null, LogLevel::CRITICAL],
\E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL],
\E_COMPILE_ERROR => [null, LogLevel::CRITICAL],
\E_PARSE => [null, LogLevel::CRITICAL],
\E_ERROR => [null, LogLevel::CRITICAL],
\E_CORE_ERROR => [null, LogLevel::CRITICAL],
];
private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
private $loggedErrors = 0;
private $configureException;
private $debug;
private $isRecursive = 0;
private $isRoot = false;
private $exceptionHandler;
private $bootstrappingLogger;
private static $reservedMemory;
private static $toStringException;
private static $silencedErrorCache = [];
private static $silencedErrorCount = 0;
private static $exitCode = 0;
/**
* Registers the error handler.
*/
public static function register(?self $handler = null, bool $replace = true): self
{
if (null === self::$reservedMemory) {
self::$reservedMemory = str_repeat('x', 32768);
register_shutdown_function(__CLASS__.'::handleFatalError');
}
if ($handlerIsNew = null === $handler) {
$handler = new static();
}
if (null === $prev = set_error_handler([$handler, 'handleError'])) {
restore_error_handler();
// Specifying the error types earlier would expose us to https://bugs.php.net/63206
set_error_handler([$handler, 'handleError'], $handler->thrownErrors | $handler->loggedErrors);
$handler->isRoot = true;
}
if ($handlerIsNew && \is_array($prev) && $prev[0] instanceof self) {
$handler = $prev[0];
$replace = false;
}
if (!$replace && $prev) {
restore_error_handler();
$handlerIsRegistered = \is_array($prev) && $handler === $prev[0];
} else {
$handlerIsRegistered = true;
}
if (\is_array($prev = set_exception_handler([$handler, 'handleException'])) && $prev[0] instanceof self) {
restore_exception_handler();
if (!$handlerIsRegistered) {
$handler = $prev[0];
} elseif ($handler !== $prev[0] && $replace) {
set_exception_handler([$handler, 'handleException']);
$p = $prev[0]->setExceptionHandler(null);
$handler->setExceptionHandler($p);
$prev[0]->setExceptionHandler($p);
}
} else {
$handler->setExceptionHandler($prev ?? [$handler, 'renderException']);
}
$handler->throwAt(\E_ALL & $handler->thrownErrors, true);
return $handler;
}
/**
* Calls a function and turns any PHP error into \ErrorException.
*
* @return mixed What $function(...$arguments) returns
*
* @throws \ErrorException When $function(...$arguments) triggers a PHP error
*/
public static function call(callable $function, ...$arguments)
{
set_error_handler(static function (int $type, string $message, string $file, int $line) {
if (__FILE__ === $file) {
$trace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3);
$file = $trace[2]['file'] ?? $file;
$line = $trace[2]['line'] ?? $line;
}
throw new \ErrorException($message, 0, $type, $file, $line);
});
try {
return $function(...$arguments);
} finally {
restore_error_handler();
}
}
public function __construct(?BufferingLogger $bootstrappingLogger = null, bool $debug = false)
{
if (\PHP_VERSION_ID < 80400) {
$this->levels[\E_STRICT] = 'Runtime Notice';
$this->loggers[\E_STRICT] = [null, LogLevel::WARNING];
}
if ($bootstrappingLogger) {
$this->bootstrappingLogger = $bootstrappingLogger;
$this->setDefaultLogger($bootstrappingLogger);
}
$traceReflector = new \ReflectionProperty(\Exception::class, 'trace');
$traceReflector->setAccessible(true);
$this->configureException = \Closure::bind(static function ($e, $trace, $file = null, $line = null) use ($traceReflector) {
$traceReflector->setValue($e, $trace);
$e->file = $file ?? $e->file;
$e->line = $line ?? $e->line;
}, null, new class() extends \Exception {
});
$this->debug = $debug;
}
/**
* Sets a logger to non assigned errors levels.
*
* @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels
* @param array|int|null $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants
* @param bool $replace Whether to replace or not any existing logger
*/
public function setDefaultLogger(LoggerInterface $logger, $levels = \E_ALL, bool $replace = false): void
{
$loggers = [];
if (\is_array($levels)) {
foreach ($levels as $type => $logLevel) {
if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) {
$loggers[$type] = [$logger, $logLevel];
}
}
} else {
if (null === $levels) {
$levels = \E_ALL;
}
foreach ($this->loggers as $type => $log) {
if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) {
$log[0] = $logger;
$loggers[$type] = $log;
}
}
}
$this->setLoggers($loggers);
}
/**
* Sets a logger for each error level.
*
* @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map
*
* @return array The previous map
*
* @throws \InvalidArgumentException
*/
public function setLoggers(array $loggers): array
{
$prevLogged = $this->loggedErrors;
$prev = $this->loggers;
$flush = [];
foreach ($loggers as $type => $log) {
if (!isset($prev[$type])) {
throw new \InvalidArgumentException('Unknown error type: '.$type);
}
if (!\is_array($log)) {
$log = [$log];
} elseif (!\array_key_exists(0, $log)) {
throw new \InvalidArgumentException('No logger provided.');
}
if (null === $log[0]) {
$this->loggedErrors &= ~$type;
} elseif ($log[0] instanceof LoggerInterface) {
$this->loggedErrors |= $type;
} else {
throw new \InvalidArgumentException('Invalid logger provided.');
}
$this->loggers[$type] = $log + $prev[$type];
if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) {
$flush[$type] = $type;
}
}
$this->reRegister($prevLogged | $this->thrownErrors);
if ($flush) {
foreach ($this->bootstrappingLogger->cleanLogs() as $log) {
$type = ThrowableUtils::getSeverity($log[2]['exception']);
if (!isset($flush[$type])) {
$this->bootstrappingLogger->log($log[0], $log[1], $log[2]);
} elseif ($this->loggers[$type][0]) {
$this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]);
}
}
}
return $prev;
}
/**
* Sets a user exception handler.
*
* @param callable(\Throwable $e)|null $handler
*
* @return callable|null The previous exception handler
*/
public function setExceptionHandler(?callable $handler): ?callable
{
$prev = $this->exceptionHandler;
$this->exceptionHandler = $handler;
return $prev;
}
/**
* Sets the PHP error levels that throw an exception when a PHP error occurs.
*
* @param int $levels A bit field of E_* constants for thrown errors
* @param bool $replace Replace or amend the previous value
*
* @return int The previous value
*/
public function throwAt(int $levels, bool $replace = false): int
{
$prev = $this->thrownErrors;
$this->thrownErrors = ($levels | \E_RECOVERABLE_ERROR | \E_USER_ERROR) & ~\E_USER_DEPRECATED & ~\E_DEPRECATED;
if (!$replace) {
$this->thrownErrors |= $prev;
}
$this->reRegister($prev | $this->loggedErrors);
return $prev;
}
/**
* Sets the PHP error levels for which local variables are preserved.
*
* @param int $levels A bit field of E_* constants for scoped errors
* @param bool $replace Replace or amend the previous value
*
* @return int The previous value
*/
public function scopeAt(int $levels, bool $replace = false): int
{
$prev = $this->scopedErrors;
$this->scopedErrors = $levels;
if (!$replace) {
$this->scopedErrors |= $prev;
}
return $prev;
}
/**
* Sets the PHP error levels for which the stack trace is preserved.
*
* @param int $levels A bit field of E_* constants for traced errors
* @param bool $replace Replace or amend the previous value
*
* @return int The previous value
*/
public function traceAt(int $levels, bool $replace = false): int
{
$prev = $this->tracedErrors;
$this->tracedErrors = $levels;
if (!$replace) {
$this->tracedErrors |= $prev;
}
return $prev;
}
/**
* Sets the error levels where the @-operator is ignored.
*
* @param int $levels A bit field of E_* constants for screamed errors
* @param bool $replace Replace or amend the previous value
*
* @return int The previous value
*/
public function screamAt(int $levels, bool $replace = false): int
{
$prev = $this->screamedErrors;
$this->screamedErrors = $levels;
if (!$replace) {
$this->screamedErrors |= $prev;
}
return $prev;
}
/**
* Re-registers as a PHP error handler if levels changed.
*/
private function reRegister(int $prev): void
{
if ($prev !== ($this->thrownErrors | $this->loggedErrors)) {
$handler = set_error_handler('is_int');
$handler = \is_array($handler) ? $handler[0] : null;
restore_error_handler();
if ($handler === $this) {
restore_error_handler();
if ($this->isRoot) {
set_error_handler([$this, 'handleError'], $this->thrownErrors | $this->loggedErrors);
} else {
set_error_handler([$this, 'handleError']);
}
}
}
}
/**
* Handles errors by filtering then logging them according to the configured bit fields.
*
* @return bool Returns false when no handling happens so that the PHP engine can handle the error itself
*
* @throws \ErrorException When $this->thrownErrors requests so
*
* @internal
*/
public function handleError(int $type, string $message, string $file, int $line): bool
{
if (\PHP_VERSION_ID >= 70300 && \E_WARNING === $type && '"' === $message[0] && false !== strpos($message, '" targeting switch is equivalent to "break')) {
$type = \E_DEPRECATED;
}
// Level is the current error reporting level to manage silent error.
$level = error_reporting();
$silenced = 0 === ($level & $type);
// Strong errors are not authorized to be silenced.
$level |= \E_RECOVERABLE_ERROR | \E_USER_ERROR | \E_DEPRECATED | \E_USER_DEPRECATED;
$log = $this->loggedErrors & $type;
$throw = $this->thrownErrors & $type & $level;
$type &= $level | $this->screamedErrors;
// Never throw on warnings triggered by assert()
if (\E_WARNING === $type && 'a' === $message[0] && 0 === strncmp($message, 'assert(): ', 10)) {
$throw = 0;
}
if (!$type || (!$log && !$throw)) {
return false;
}
$logMessage = $this->levels[$type].': '.$message;
if (null !== self::$toStringException) {
$errorAsException = self::$toStringException;
self::$toStringException = null;
} elseif (!$throw && !($type & $level)) {
if (!isset(self::$silencedErrorCache[$id = $file.':'.$line])) {
$lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 5), $type, $file, $line, false) : [];
$errorAsException = new SilencedErrorContext($type, $file, $line, isset($lightTrace[1]) ? [$lightTrace[0]] : $lightTrace);
} elseif (isset(self::$silencedErrorCache[$id][$message])) {
$lightTrace = null;
$errorAsException = self::$silencedErrorCache[$id][$message];
++$errorAsException->count;
} else {
$lightTrace = [];
$errorAsException = null;
}
if (100 < ++self::$silencedErrorCount) {
self::$silencedErrorCache = $lightTrace = [];
self::$silencedErrorCount = 1;
}
if ($errorAsException) {
self::$silencedErrorCache[$id][$message] = $errorAsException;
}
if (null === $lightTrace) {
return true;
}
} else {
if (PHP_VERSION_ID < 80303 && false !== strpos($message, '@anonymous')) {
$backtrace = debug_backtrace(false, 5);
for ($i = 1; isset($backtrace[$i]); ++$i) {
if (isset($backtrace[$i]['function'], $backtrace[$i]['args'][0])
&& ('trigger_error' === $backtrace[$i]['function'] || 'user_error' === $backtrace[$i]['function'])
) {
if ($backtrace[$i]['args'][0] !== $message) {
$message = $backtrace[$i]['args'][0];
}
break;
}
}
}
if (false !== strpos($message, "@anonymous\0")) {
$message = $this->parseAnonymousClass($message);
$logMessage = $this->levels[$type].': '.$message;
}
$errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line);
if ($throw || $this->tracedErrors & $type) {
$backtrace = $errorAsException->getTrace();
$lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw);
($this->configureException)($errorAsException, $lightTrace, $file, $line);
} else {
($this->configureException)($errorAsException, []);
$backtrace = [];
}
}
if ($throw) {
if (\PHP_VERSION_ID < 70400 && \E_USER_ERROR & $type) {
for ($i = 1; isset($backtrace[$i]); ++$i) {
if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function'])
&& '__toString' === $backtrace[$i]['function']
&& '->' === $backtrace[$i]['type']
&& !isset($backtrace[$i - 1]['class'])
&& ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function'])
) {
// Here, we know trigger_error() has been called from __toString().
// PHP triggers a fatal error when throwing from __toString().
// A small convention allows working around the limitation:
// given a caught $e exception in __toString(), quitting the method with
// `return trigger_error($e, E_USER_ERROR);` allows this error handler
// to make $e get through the __toString() barrier.
$context = 4 < \func_num_args() ? (func_get_arg(4) ?: []) : [];
foreach ($context as $e) {
if ($e instanceof \Throwable && $e->__toString() === $message) {
self::$toStringException = $e;
return true;
}
}
// Display the original error message instead of the default one.
$exitCode = self::$exitCode;
try {
$this->handleException($errorAsException);
} finally {
self::$exitCode = $exitCode;
}
// Stop the process by giving back the error to the native handler.
return false;
}
}
}
throw $errorAsException;
}
if ($this->isRecursive) {
$log = 0;
} else {
if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404)) {
$currentErrorHandler = set_error_handler('is_int');
restore_error_handler();
}
try {
$this->isRecursive = true;
$level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG;
$this->loggers[$type][0]->log($level, $logMessage, $errorAsException ? ['exception' => $errorAsException] : []);
} finally {
$this->isRecursive = false;
if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404)) {
set_error_handler($currentErrorHandler);
}
}
}
return !$silenced && $type && $log;
}
/**
* Handles an exception by logging then forwarding it to another handler.
*
* @internal
*/
public function handleException(\Throwable $exception)
{
$handlerException = null;
if (!$exception instanceof FatalError) {
self::$exitCode = 255;
$type = ThrowableUtils::getSeverity($exception);
} else {
$type = $exception->getError()['type'];
}
if ($this->loggedErrors & $type) {
if (false !== strpos($message = $exception->getMessage(), "@anonymous\0")) {
$message = $this->parseAnonymousClass($message);
}
if ($exception instanceof FatalError) {
$message = 'Fatal '.$message;
} elseif ($exception instanceof \Error) {
$message = 'Uncaught Error: '.$message;
} elseif ($exception instanceof \ErrorException) {
$message = 'Uncaught '.$message;
} else {
$message = 'Uncaught Exception: '.$message;
}
try {
$this->loggers[$type][0]->log($this->loggers[$type][1], $message, ['exception' => $exception]);
} catch (\Throwable $handlerException) {
}
}
if (!$exception instanceof OutOfMemoryError) {
foreach ($this->getErrorEnhancers() as $errorEnhancer) {
if ($e = $errorEnhancer->enhance($exception)) {
$exception = $e;
break;
}
}
}
$exceptionHandler = $this->exceptionHandler;
$this->exceptionHandler = [$this, 'renderException'];
if (null === $exceptionHandler || $exceptionHandler === $this->exceptionHandler) {
$this->exceptionHandler = null;
}
try {
if (null !== $exceptionHandler) {
return $exceptionHandler($exception);
}
$handlerException = $handlerException ?: $exception;
} catch (\Throwable $handlerException) {
}
if ($exception === $handlerException && null === $this->exceptionHandler) {
self::$reservedMemory = null; // Disable the fatal error handler
throw $exception; // Give back $exception to the native handler
}
$loggedErrors = $this->loggedErrors;
if ($exception === $handlerException) {
$this->loggedErrors &= ~$type;
}
try {
$this->handleException($handlerException);
} finally {
$this->loggedErrors = $loggedErrors;
}
}
/**
* Shutdown registered function for handling PHP fatal errors.
*
* @param array|null $error An array as returned by error_get_last()
*
* @internal
*/
public static function handleFatalError(?array $error = null): void
{
if (null === self::$reservedMemory) {
return;
}
$handler = self::$reservedMemory = null;
$handlers = [];
$previousHandler = null;
$sameHandlerLimit = 10;
while (!\is_array($handler) || !$handler[0] instanceof self) {
$handler = set_exception_handler('is_int');
restore_exception_handler();
if (!$handler) {
break;
}
restore_exception_handler();
if ($handler !== $previousHandler) {
array_unshift($handlers, $handler);
$previousHandler = $handler;
} elseif (0 === --$sameHandlerLimit) {
$handler = null;
break;
}
}
foreach ($handlers as $h) {
set_exception_handler($h);
}
if (!$handler) {
if (null === $error && $exitCode = self::$exitCode) {
register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
}
return;
}
if ($handler !== $h) {
$handler[0]->setExceptionHandler($h);
}
$handler = $handler[0];
$handlers = [];
if ($exit = null === $error) {
$error = error_get_last();
}
if ($error && $error['type'] &= \E_PARSE | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR) {
// Let's not throw anymore but keep logging
$handler->throwAt(0, true);
$trace = $error['backtrace'] ?? null;
if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
$fatalError = new OutOfMemoryError($handler->levels[$error['type']].': '.$error['message'], 0, $error, 2, false, $trace);
} else {
$fatalError = new FatalError($handler->levels[$error['type']].': '.$error['message'], 0, $error, 2, true, $trace);
}
} else {
$fatalError = null;
}
try {
if (null !== $fatalError) {
self::$exitCode = 255;
$handler->handleException($fatalError);
}
} catch (FatalError $e) {
// Ignore this re-throw
}
if ($exit && $exitCode = self::$exitCode) {
register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
}
}
/**
* Renders the given exception.
*
* As this method is mainly called during boot where nothing is yet available,
* the output is always either HTML or CLI depending where PHP runs.
*/
private function renderException(\Throwable $exception): void
{
$renderer = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliErrorRenderer() : new HtmlErrorRenderer($this->debug);
$exception = $renderer->render($exception);
if (!headers_sent()) {
http_response_code($exception->getStatusCode());
foreach ($exception->getHeaders() as $name => $value) {
header($name.': '.$value, false);
}
}
echo $exception->getAsString();
}
/**
* Override this method if you want to define more error enhancers.
*
* @return ErrorEnhancerInterface[]
*/
protected function getErrorEnhancers(): iterable
{
return [
new UndefinedFunctionErrorEnhancer(),
new UndefinedMethodErrorEnhancer(),
new ClassNotFoundErrorEnhancer(),
];
}
/**
* Cleans the trace by removing function arguments and the frames added by the error handler and DebugClassLoader.
*/
private function cleanTrace(array $backtrace, int $type, string &$file, int &$line, bool $throw): array
{
$lightTrace = $backtrace;
for ($i = 0; isset($backtrace[$i]); ++$i) {
if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) {
$lightTrace = \array_slice($lightTrace, 1 + $i);
break;
}
}
if (\E_USER_DEPRECATED === $type) {
for ($i = 0; isset($lightTrace[$i]); ++$i) {
if (!isset($lightTrace[$i]['file'], $lightTrace[$i]['line'], $lightTrace[$i]['function'])) {
continue;
}
if (!isset($lightTrace[$i]['class']) && 'trigger_deprecation' === $lightTrace[$i]['function']) {
$file = $lightTrace[$i]['file'];
$line = $lightTrace[$i]['line'];
$lightTrace = \array_slice($lightTrace, 1 + $i);
break;
}
}
}
if (class_exists(DebugClassLoader::class, false)) {
for ($i = \count($lightTrace) - 2; 0 < $i; --$i) {
if (DebugClassLoader::class === ($lightTrace[$i]['class'] ?? null)) {
array_splice($lightTrace, --$i, 2);
}
}
}
if (!($throw || $this->scopedErrors & $type)) {
for ($i = 0; isset($lightTrace[$i]); ++$i) {
unset($lightTrace[$i]['args'], $lightTrace[$i]['object']);
}
}
return $lightTrace;
}
/**
* Parse the error message by removing the anonymous class notation
* and using the parent class instead if possible.
*/
private function parseAnonymousClass(string $message): string
{
return preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static function ($m) {
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
}, $message);
}
}

View file

@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\ErrorRenderer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
// Help opcache.preload discover always-needed symbols
class_exists(CliDumper::class);
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class CliErrorRenderer implements ErrorRendererInterface
{
/**
* {@inheritdoc}
*/
public function render(\Throwable $exception): FlattenException
{
$cloner = new VarCloner();
$dumper = new class() extends CliDumper {
protected function supportsColors(): bool
{
$outputStream = $this->outputStream;
$this->outputStream = fopen('php://stdout', 'w');
try {
return parent::supportsColors();
} finally {
$this->outputStream = $outputStream;
}
}
};
return FlattenException::createFromThrowable($exception)
->setAsString($dumper->dump($cloner->cloneVar($exception), true));
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\ErrorRenderer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
/**
* Formats an exception to be used as response content.
*
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
interface ErrorRendererInterface
{
/**
* Renders a Throwable as a FlattenException.
*/
public function render(\Throwable $exception): FlattenException;
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,92 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\ErrorRenderer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
use Symfony\Component\Serializer\SerializerInterface;
/**
* Formats an exception using Serializer for rendering.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class SerializerErrorRenderer implements ErrorRendererInterface
{
private $serializer;
private $format;
private $fallbackErrorRenderer;
private $debug;
/**
* @param string|callable(FlattenException) $format The format as a string or a callable that should return it
* formats not supported by Request::getMimeTypes() should be given as mime types
* @param bool|callable $debug The debugging mode as a boolean or a callable that should return it
*/
public function __construct(SerializerInterface $serializer, $format, ?ErrorRendererInterface $fallbackErrorRenderer = null, $debug = false)
{
if (!\is_string($format) && !\is_callable($format)) {
throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be a string or a callable, "%s" given.', __METHOD__, \gettype($format)));
}
if (!\is_bool($debug) && !\is_callable($debug)) {
throw new \TypeError(sprintf('Argument 4 passed to "%s()" must be a boolean or a callable, "%s" given.', __METHOD__, \gettype($debug)));
}
$this->serializer = $serializer;
$this->format = $format;
$this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer();
$this->debug = $debug;
}
/**
* {@inheritdoc}
*/
public function render(\Throwable $exception): FlattenException
{
$headers = ['Vary' => 'Accept'];
$debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception);
if ($debug) {
$headers['X-Debug-Exception'] = rawurlencode(substr($exception->getMessage(), 0, 2000));
$headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine();
}
$flattenException = FlattenException::createFromThrowable($exception, null, $headers);
try {
$format = \is_string($this->format) ? $this->format : ($this->format)($flattenException);
$headers['Content-Type'] = Request::getMimeTypes($format)[0] ?? $format;
$flattenException->setAsString($this->serializer->serialize($flattenException, $format, [
'exception' => $exception,
'debug' => $debug,
]));
} catch (NotEncodableValueException $e) {
$flattenException = $this->fallbackErrorRenderer->render($exception);
}
return $flattenException->setHeaders($flattenException->getHeaders() + $headers);
}
public static function getPreferredFormat(RequestStack $requestStack): \Closure
{
return static function () use ($requestStack) {
if (!$request = $requestStack->getCurrentRequest()) {
throw new NotEncodableValueException();
}
return $request->getPreferredFormat();
};
}
}

View file

@ -0,0 +1,427 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Exception;
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
/**
* FlattenException wraps a PHP Error or Exception to be able to serialize it.
*
* Basically, this class removes all objects from the trace.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FlattenException
{
/** @var string */
private $message;
/** @var int|string */
private $code;
/** @var self|null */
private $previous;
/** @var array */
private $trace;
/** @var string */
private $traceAsString;
/** @var string */
private $class;
/** @var int */
private $statusCode;
/** @var string */
private $statusText;
/** @var array */
private $headers;
/** @var string */
private $file;
/** @var int */
private $line;
/** @var string|null */
private $asString;
/**
* @return static
*/
public static function create(\Exception $exception, ?int $statusCode = null, array $headers = []): self
{
return static::createFromThrowable($exception, $statusCode, $headers);
}
/**
* @return static
*/
public static function createFromThrowable(\Throwable $exception, ?int $statusCode = null, array $headers = []): self
{
$e = new static();
$e->setMessage($exception->getMessage());
$e->setCode($exception->getCode());
if ($exception instanceof HttpExceptionInterface) {
$statusCode = $exception->getStatusCode();
$headers = array_merge($headers, $exception->getHeaders());
} elseif ($exception instanceof RequestExceptionInterface) {
$statusCode = 400;
}
if (null === $statusCode) {
$statusCode = 500;
}
if (class_exists(Response::class) && isset(Response::$statusTexts[$statusCode])) {
$statusText = Response::$statusTexts[$statusCode];
} else {
$statusText = 'Whoops, looks like something went wrong.';
}
$e->setStatusText($statusText);
$e->setStatusCode($statusCode);
$e->setHeaders($headers);
$e->setTraceFromThrowable($exception);
$e->setClass(\get_class($exception));
$e->setFile($exception->getFile());
$e->setLine($exception->getLine());
$previous = $exception->getPrevious();
if ($previous instanceof \Throwable) {
$e->setPrevious(static::createFromThrowable($previous));
}
return $e;
}
public function toArray(): array
{
$exceptions = [];
foreach (array_merge([$this], $this->getAllPrevious()) as $exception) {
$exceptions[] = [
'message' => $exception->getMessage(),
'class' => $exception->getClass(),
'trace' => $exception->getTrace(),
];
}
return $exceptions;
}
public function getStatusCode(): int
{
return $this->statusCode;
}
/**
* @return $this
*/
public function setStatusCode(int $code): self
{
$this->statusCode = $code;
return $this;
}
public function getHeaders(): array
{
return $this->headers;
}
/**
* @return $this
*/
public function setHeaders(array $headers): self
{
$this->headers = $headers;
return $this;
}
public function getClass(): string
{
return $this->class;
}
/**
* @return $this
*/
public function setClass(string $class): self
{
$this->class = false !== strpos($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class;
return $this;
}
public function getFile(): string
{
return $this->file;
}
/**
* @return $this
*/
public function setFile(string $file): self
{
$this->file = $file;
return $this;
}
public function getLine(): int
{
return $this->line;
}
/**
* @return $this
*/
public function setLine(int $line): self
{
$this->line = $line;
return $this;
}
public function getStatusText(): string
{
return $this->statusText;
}
/**
* @return $this
*/
public function setStatusText(string $statusText): self
{
$this->statusText = $statusText;
return $this;
}
public function getMessage(): string
{
return $this->message;
}
/**
* @return $this
*/
public function setMessage(string $message): self
{
if (false !== strpos($message, "@anonymous\0")) {
$message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
}, $message);
}
$this->message = $message;
return $this;
}
/**
* @return int|string int most of the time (might be a string with PDOException)
*/
public function getCode()
{
return $this->code;
}
/**
* @param int|string $code
*
* @return $this
*/
public function setCode($code): self
{
$this->code = $code;
return $this;
}
public function getPrevious(): ?self
{
return $this->previous;
}
/**
* @return $this
*/
public function setPrevious(?self $previous): self
{
$this->previous = $previous;
return $this;
}
/**
* @return self[]
*/
public function getAllPrevious(): array
{
$exceptions = [];
$e = $this;
while ($e = $e->getPrevious()) {
$exceptions[] = $e;
}
return $exceptions;
}
public function getTrace(): array
{
return $this->trace;
}
/**
* @return $this
*/
public function setTraceFromThrowable(\Throwable $throwable): self
{
$this->traceAsString = $throwable->getTraceAsString();
return $this->setTrace($throwable->getTrace(), $throwable->getFile(), $throwable->getLine());
}
/**
* @return $this
*/
public function setTrace(array $trace, ?string $file, ?int $line): self
{
$this->trace = [];
$this->trace[] = [
'namespace' => '',
'short_class' => '',
'class' => '',
'type' => '',
'function' => '',
'file' => $file,
'line' => $line,
'args' => [],
];
foreach ($trace as $entry) {
$class = '';
$namespace = '';
if (isset($entry['class'])) {
$parts = explode('\\', $entry['class']);
$class = array_pop($parts);
$namespace = implode('\\', $parts);
}
$this->trace[] = [
'namespace' => $namespace,
'short_class' => $class,
'class' => $entry['class'] ?? '',
'type' => $entry['type'] ?? '',
'function' => $entry['function'] ?? null,
'file' => $entry['file'] ?? null,
'line' => $entry['line'] ?? null,
'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : [],
];
}
return $this;
}
private function flattenArgs(array $args, int $level = 0, int &$count = 0): array
{
$result = [];
foreach ($args as $key => $value) {
if (++$count > 1e4) {
return ['array', '*SKIPPED over 10000 entries*'];
}
if ($value instanceof \__PHP_Incomplete_Class) {
$result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)];
} elseif (\is_object($value)) {
$result[$key] = ['object', \get_class($value)];
} elseif (\is_array($value)) {
if ($level > 10) {
$result[$key] = ['array', '*DEEP NESTED ARRAY*'];
} else {
$result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)];
}
} elseif (null === $value) {
$result[$key] = ['null', null];
} elseif (\is_bool($value)) {
$result[$key] = ['boolean', $value];
} elseif (\is_int($value)) {
$result[$key] = ['integer', $value];
} elseif (\is_float($value)) {
$result[$key] = ['float', $value];
} elseif (\is_resource($value)) {
$result[$key] = ['resource', get_resource_type($value)];
} else {
$result[$key] = ['string', (string) $value];
}
}
return $result;
}
private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value): string
{
$array = new \ArrayObject($value);
return $array['__PHP_Incomplete_Class_Name'];
}
public function getTraceAsString(): string
{
return $this->traceAsString;
}
/**
* @return $this
*/
public function setAsString(?string $asString): self
{
$this->asString = $asString;
return $this;
}
public function getAsString(): string
{
if (null !== $this->asString) {
return $this->asString;
}
$message = '';
$next = false;
foreach (array_reverse(array_merge([$this], $this->getAllPrevious())) as $exception) {
if ($next) {
$message .= 'Next ';
} else {
$next = true;
}
$message .= $exception->getClass();
if ('' != $exception->getMessage()) {
$message .= ': '.$exception->getMessage();
}
$message .= ' in '.$exception->getFile().':'.$exception->getLine().
"\nStack trace:\n".$exception->getTraceAsString()."\n\n";
}
return rtrim($message);
}
}

View file

@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Exception;
/**
* Data Object that represents a Silenced Error.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class SilencedErrorContext implements \JsonSerializable
{
public $count = 1;
private $severity;
private $file;
private $line;
private $trace;
public function __construct(int $severity, string $file, int $line, array $trace = [], int $count = 1)
{
$this->severity = $severity;
$this->file = $file;
$this->line = $line;
$this->trace = $trace;
$this->count = $count;
}
public function getSeverity(): int
{
return $this->severity;
}
public function getFile(): string
{
return $this->file;
}
public function getLine(): int
{
return $this->line;
}
public function getTrace(): array
{
return $this->trace;
}
public function jsonSerialize(): array
{
return [
'severity' => $this->severity,
'file' => $this->file,
'line' => $this->line,
'trace' => $this->trace,
'count' => $this->count,
];
}
}

File diff suppressed because it is too large Load diff

19
vendor/symfony/error-handler/LICENSE vendored Normal file
View file

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

44
vendor/symfony/error-handler/README.md vendored Normal file
View file

@ -0,0 +1,44 @@
ErrorHandler Component
======================
The ErrorHandler component provides tools to manage errors and ease debugging PHP code.
Getting Started
---------------
```
$ composer require symfony/error-handler
```
```php
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Component\ErrorHandler\DebugClassLoader;
Debug::enable();
// or enable only one feature
//ErrorHandler::register();
//DebugClassLoader::enable();
// If you want a custom generic template when debug is not enabled
// HtmlErrorRenderer::setTemplate('/path/to/custom/error.html.php');
$data = ErrorHandler::call(static function () use ($filename, $datetimeFormat) {
// if any code executed inside this anonymous function fails, a PHP exception
// will be thrown, even if the code uses the '@' PHP silence operator
$data = json_decode(file_get_contents($filename), true);
$data['read_at'] = date($datetimeFormat);
file_put_contents($filename, json_encode($data));
return $data;
});
```
Resources
---------
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

View file

@ -0,0 +1,4 @@
body { background-color: #fff; color: #222; font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; margin: 0; }
.container { margin: 30px; max-width: 600px; }
h1 { color: #dc3545; font-size: 24px; }
h2 { font-size: 18px; }

View file

@ -0,0 +1,252 @@
/* This file is based on WebProfilerBundle/Resources/views/Profiler/profiler.css.twig.
If you make any change in this file, verify the same change is needed in the other file. */
:root {
--font-sans-serif: Helvetica, Arial, sans-serif;
--page-background: #f9f9f9;
--color-text: #222;
/* when updating any of these colors, do the same in toolbar.css.twig */
--color-success: #4f805d;
--color-warning: #a46a1f;
--color-error: #b0413e;
--color-muted: #999;
--tab-background: #fff;
--tab-color: #444;
--tab-active-background: #666;
--tab-active-color: #fafafa;
--tab-disabled-background: #f5f5f5;
--tab-disabled-color: #999;
--metric-value-background: #fff;
--metric-value-color: inherit;
--metric-unit-color: #999;
--metric-label-background: #e0e0e0;
--metric-label-color: inherit;
--table-border: #e0e0e0;
--table-background: #fff;
--table-header: #e0e0e0;
--trace-selected-background: #F7E5A1;
--tree-active-background: #F7E5A1;
--exception-title-color: var(--base-2);
--shadow: 0px 0px 1px rgba(128, 128, 128, .2);
--border: 1px solid #e0e0e0;
--background-error: var(--color-error);
--highlight-comment: #969896;
--highlight-default: #222222;
--highlight-keyword: #a71d5d;
--highlight-string: #183691;
--base-0: #fff;
--base-1: #f5f5f5;
--base-2: #e0e0e0;
--base-3: #ccc;
--base-4: #666;
--base-5: #444;
--base-6: #222;
}
.theme-dark {
--page-background: #36393e;
--color-text: #e0e0e0;
--color-muted: #777;
--color-error: #d43934;
--tab-background: #555;
--tab-color: #ccc;
--tab-active-background: #888;
--tab-active-color: #fafafa;
--tab-disabled-background: var(--page-background);
--tab-disabled-color: #777;
--metric-value-background: #555;
--metric-value-color: inherit;
--metric-unit-color: #999;
--metric-label-background: #777;
--metric-label-color: #e0e0e0;
--trace-selected-background: #71663acc;
--table-border: #444;
--table-background: #333;
--table-header: #555;
--info-background: rgba(79, 148, 195, 0.5);
--tree-active-background: var(--metric-label-background);
--exception-title-color: var(--base-2);
--shadow: 0px 0px 1px rgba(32, 32, 32, .2);
--border: 1px solid #666;
--background-error: #b0413e;
--highlight-comment: #dedede;
--highlight-default: var(--base-6);
--highlight-keyword: #ff413c;
--highlight-string: #70a6fd;
--base-0: #2e3136;
--base-1: #444;
--base-2: #666;
--base-3: #666;
--base-4: #666;
--base-5: #e0e0e0;
--base-6: #f5f5f5;
--card-label-background: var(--tab-active-background);
--card-label-color: var(--tab-active-color);
}
html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}
html {
/* always display the vertical scrollbar to avoid jumps when toggling contents */
overflow-y: scroll;
}
body { background-color: var(--page-background); color: var(--base-6); font: 14px/1.4 Helvetica, Arial, sans-serif; padding-bottom: 45px; }
a { cursor: pointer; text-decoration: none; }
a:hover { text-decoration: underline; }
abbr[title] { border-bottom: none; cursor: help; text-decoration: none; }
code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; }
table, tr, th, td { background: var(--base-0); border-collapse: collapse; vertical-align: top; }
table { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; }
table th, table td { border: solid var(--base-2); border-width: 1px 0; padding: 8px 10px; }
table th { background-color: var(--base-2); font-weight: bold; text-align: left; }
.m-t-5 { margin-top: 5px; }
.hidden-xs-down { display: none; }
.block { display: block; }
.full-width { width: 100%; }
.hidden { display: none; }
.prewrap { white-space: pre-wrap; }
.nowrap { white-space: nowrap; }
.newline { display: block; }
.break-long-words { word-wrap: break-word; overflow-wrap: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; min-width: 0; }
.text-small { font-size: 12px !important; }
.text-muted { color: #999; }
.text-bold { font-weight: bold; }
.empty { border: 4px dashed var(--base-2); color: #999; margin: 1em 0; padding: .5em 2em; }
.status-success { background: rgba(94, 151, 110, 0.3); }
.status-warning { background: rgba(240, 181, 24, 0.3); }
.status-error { background: rgba(176, 65, 62, 0.2); }
.status-success td, .status-warning td, .status-error td { background: transparent; }
tr.status-error td, tr.status-warning td { border-bottom: 1px solid var(--base-2); border-top: 1px solid var(--base-2); }
.status-warning .colored { color: #A46A1F; }
.status-error .colored { color: var(--color-error); }
.sf-toggle { cursor: pointer; position: relative; }
.sf-toggle-content { -moz-transition: display .25s ease; -webkit-transition: display .25s ease; transition: display .25s ease; }
.sf-toggle-content.sf-toggle-hidden { display: none; }
.sf-toggle-content.sf-toggle-visible { display: block; }
thead.sf-toggle-content.sf-toggle-visible, tbody.sf-toggle-content.sf-toggle-visible { display: table-row-group; }
.sf-toggle-off .icon-close, .sf-toggle-on .icon-open { display: none; }
.sf-toggle-off .icon-open, .sf-toggle-on .icon-close { display: block; }
.tab-navigation { margin: 0 0 1em 0; padding: 0; }
.tab-navigation li { background: var(--tab-background); border: 1px solid var(--table-border); color: var(--tab-color); cursor: pointer; display: inline-block; font-size: 16px; margin: 0 0 0 -1px; padding: .5em .75em; z-index: 1; }
.tab-navigation li .badge { background-color: var(--base-1); color: var(--base-4); display: inline-block; font-size: 14px; font-weight: bold; margin-left: 8px; min-width: 10px; padding: 1px 6px; text-align: center; white-space: nowrap; }
.tab-navigation li.disabled { background: var(--tab-disabled-background); color: var(--tab-disabled-color); }
.tab-navigation li.active { background: var(--tab-active-background); color: var(--tab-active-color); z-index: 1100; }
.tab-navigation li.active .badge { background-color: var(--base-5); color: var(--base-2); }
.tab-content > *:first-child { margin-top: 0; }
.tab-navigation li .badge.status-warning { background: var(--color-warning); color: #FFF; }
.tab-navigation li .badge.status-error { background: var(--background-error); color: #FFF; }
.sf-tabs .tab:not(:first-child) { display: none; }
[data-filters] { position: relative; }
[data-filtered] { cursor: pointer; }
[data-filtered]:after { content: '\00a0\25BE'; }
[data-filtered]:hover .filter-list li { display: inline-flex; }
[class*="filter-hidden-"] { display: none; }
.filter-list { position: absolute; border: var(--border); box-shadow: var(--shadow); margin: 0; padding: 0; display: flex; flex-direction: column; }
.filter-list :after { content: ''; }
.filter-list li {
background: var(--tab-disabled-background);
border-bottom: var(--border);
color: var(--tab-disabled-color);
display: none;
list-style: none;
margin: 0;
padding: 5px 10px;
text-align: left;
font-weight: normal;
}
.filter-list li.active {
background: var(--tab-background);
color: var(--tab-color);
}
.filter-list li.last-active {
background: var(--tab-active-background);
color: var(--tab-active-color);
}
.filter-list-level li { cursor: s-resize; }
.filter-list-level li.active { cursor: n-resize; }
.filter-list-level li.last-active { cursor: default; }
.filter-list-level li.last-active:before { content: '\2714\00a0'; }
.filter-list-choice li:before { content: '\2714\00a0'; color: transparent; }
.filter-list-choice li.active:before { color: unset; }
.container { max-width: 1024px; margin: 0 auto; padding: 0 15px; }
.container::after { content: ""; display: table; clear: both; }
header { background-color: #222; color: rgba(255, 255, 255, 0.75); font-size: 13px; height: 33px; line-height: 33px; padding: 0; }
header .container { display: flex; justify-content: space-between; }
.logo { flex: 1; font-size: 13px; font-weight: normal; margin: 0; padding: 0; }
.logo svg { height: 18px; width: 18px; opacity: .8; vertical-align: -5px; }
.help-link { margin-left: 15px; }
.help-link a { color: inherit; }
.help-link .icon svg { height: 15px; width: 15px; opacity: .7; vertical-align: -2px; }
.help-link a:hover { color: #EEE; text-decoration: none; }
.help-link a:hover svg { opacity: .9; }
.exception-summary { background: var(--background-error); border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 15px; }
.exception-metadata { background: rgba(0, 0, 0, 0.1); padding: 7px 0; }
.exception-metadata .container { display: flex; flex-direction: row; justify-content: space-between; }
.exception-metadata h2, .exception-metadata h2 > a { color: rgba(255, 255, 255, 0.8); font-size: 13px; font-weight: 400; margin: 0; }
.exception-http small { font-size: 13px; opacity: .7; }
.exception-hierarchy { flex: 1; }
.exception-hierarchy .icon { margin: 0 3px; opacity: .7; }
.exception-hierarchy .icon svg { height: 13px; width: 13px; vertical-align: -2px; }
.exception-without-message .exception-message-wrapper { display: none; }
.exception-message-wrapper .container { display: flex; align-items: flex-start; min-height: 70px; padding: 10px 15px 8px; }
.exception-message { flex-grow: 1; }
.exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; }
.exception-message.long { font-size: 18px; }
.exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; }
.exception-message a:hover { border-bottom-color: #ffffff; }
.exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; }
.trace + .trace { margin-top: 30px; }
.trace-head { background-color: var(--base-2); padding: 10px; position: relative; }
.trace-head .trace-class { color: var(--base-6); font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; }
.trace-head .trace-namespace { color: #999; display: block; font-size: 13px; }
.trace-head .icon { position: absolute; right: 0; top: 0; }
.trace-head .icon svg { fill: var(--base-5); height: 24px; width: 24px; }
.trace-details { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; table-layout: fixed; }
.trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; }
.trace-line { position: relative; padding-top: 8px; padding-bottom: 8px; }
.trace-line + .trace-line { border-top: var(--border); }
.trace-line:hover { background: var(--base-1); }
.trace-line a { color: var(--base-6); }
.trace-line .icon { opacity: .4; position: absolute; left: 10px; }
.trace-line .icon svg { fill: var(--base-5); height: 16px; width: 16px; }
.trace-line .icon.icon-copy { left: auto; top: auto; padding-left: 5px; display: none }
.trace-line:hover .icon.icon-copy:not(.hidden) { display: inline-block }
.trace-line-header { padding-left: 36px; padding-right: 10px; }
.trace-file-path, .trace-file-path a { color: var(--base-6); font-size: 13px; }
.trace-class { color: var(--color-error); }
.trace-type { padding: 0 2px; }
.trace-method { color: var(--color-error); font-weight: bold; }
.trace-arguments { color: #777; font-weight: normal; padding-left: 2px; }
.trace-code { background: var(--base-0); font-size: 12px; margin: 10px 10px 2px 10px; padding: 10px; overflow-x: auto; white-space: nowrap; }
.trace-code ol { margin: 0; float: left; }
.trace-code li { color: #969896; margin: 0; padding-left: 10px; float: left; width: 100%; }
.trace-code li + li { margin-top: 5px; }
.trace-code li.selected { background: var(--trace-selected-background); margin-top: 2px; }
.trace-code li code { color: var(--base-6); white-space: pre; }
.trace-as-text .stacktrace { line-height: 1.8; margin: 0 0 15px; white-space: pre-wrap; }
@media (min-width: 575px) {
.hidden-xs-down { display: initial; }
.help-link { margin-left: 30px; }
}

View file

@ -0,0 +1,128 @@
.sf-reset .traces {
padding-bottom: 14px;
}
.sf-reset .traces li {
font-size: 12px;
color: #868686;
padding: 5px 4px;
list-style-type: decimal;
margin-left: 20px;
}
.sf-reset #logs .traces li.error {
font-style: normal;
color: #AA3333;
background: #f9ecec;
}
.sf-reset #logs .traces li.warning {
font-style: normal;
background: #ffcc00;
}
/* fix for Opera not liking empty <li> */
.sf-reset .traces li:after {
content: "\00A0";
}
.sf-reset .trace {
border: 1px solid #D3D3D3;
padding: 10px;
overflow: auto;
margin: 10px 0 20px;
}
.sf-reset .block-exception {
-moz-border-radius: 16px;
-webkit-border-radius: 16px;
border-radius: 16px;
margin-bottom: 20px;
background-color: #f6f6f6;
border: 1px solid #dfdfdf;
padding: 30px 28px;
word-wrap: break-word;
overflow: hidden;
}
.sf-reset .block-exception div {
color: #313131;
font-size: 10px;
}
.sf-reset .block-exception-detected .illustration-exception,
.sf-reset .block-exception-detected .text-exception {
float: left;
}
.sf-reset .block-exception-detected .illustration-exception {
width: 152px;
}
.sf-reset .block-exception-detected .text-exception {
width: 670px;
padding: 30px 44px 24px 46px;
position: relative;
}
.sf-reset .text-exception .open-quote,
.sf-reset .text-exception .close-quote {
font-family: Arial, Helvetica, sans-serif;
position: absolute;
color: #C9C9C9;
font-size: 8em;
}
.sf-reset .open-quote {
top: 0;
left: 0;
}
.sf-reset .close-quote {
bottom: -0.5em;
right: 50px;
}
.sf-reset .block-exception p {
font-family: Arial, Helvetica, sans-serif;
}
.sf-reset .block-exception p a,
.sf-reset .block-exception p a:hover {
color: #565656;
}
.sf-reset .logs h2 {
float: left;
width: 654px;
}
.sf-reset .error-count, .sf-reset .support {
float: right;
width: 170px;
text-align: right;
}
.sf-reset .error-count span {
display: inline-block;
background-color: #aacd4e;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
border-radius: 6px;
padding: 4px;
color: white;
margin-right: 2px;
font-size: 11px;
font-weight: bold;
}
.sf-reset .support a {
display: inline-block;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
border-radius: 6px;
padding: 4px;
color: #000000;
margin-right: 2px;
font-size: 11px;
font-weight: bold;
}
.sf-reset .toggle {
vertical-align: middle;
}
.sf-reset .linked ul,
.sf-reset .linked li {
display: inline;
}
.sf-reset #output-content {
color: #000;
font-size: 12px;
}
.sf-reset #traces-text pre {
white-space: pre;
font-size: 12px;
font-family: monospace;
}

View file

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path fill="#FFF" d="M1363 877l-742 742q-19 19-45 19t-45-19l-166-166q-19-19-19-45t19-45l531-531-531-531q-19-19-19-45t19-45L531 45q19-19 45-19t45 19l742 742q19 19 19 45t-19 45z"/></svg>

After

Width:  |  Height:  |  Size: 276 B

View file

@ -0,0 +1 @@
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAgCAYAAAABtRhCAAADVUlEQVRIx82XX0jTURTHLYPyqZdefQx66CEo80+aYpoIkqzUikz6Z5klQoWUWYRIJYEUGpQ+lIr9U5dOTLdCtkmWZis3rbnC5fw/neYW002307mX/cZvP3/7o1PwwOdh95x7vnf39zvnd29AgBer2xO6DclAXiMqZAqxIiNIN/IYSUS2BPhjmGATchUxI+ADWiRhpWK7HKuHFVBFdmU5YvnI4grFGCaReF/EBH4KsZlGgj2JBTuCYBWRIYF8YoEOJ6wBt/gEs7mBbyOjQXruPLSdOgPCiEiPSUUHDoL8Ug5IUo9B/d5wrt+G7OAKNrODPuVdB6vRCIzN6SdBlpW9RIgk/1FeAXabzRlrUPVCS/JhbmwudztnGeeH9AyXBIwtmM3wLinZJZHifjHw2V+NBoRh+9ixQrbgbnaSIcl7cGea6hoXQbNe7za241oeO5Z0p42M4BV2EqP2D50wo+6HzvwC6C4sApNOR8cmOrtcnhtj2kYRyC9eBvXzKrBZrXSs72kFd1t3MoKVbMekQkEnSNKOO8fac3LpmK6l1TlGtsxmsdKFsecPYgwxst0cwROMYDXboSotg0WLBRqjY51jLYcENElXwW2XJKPydvoI2GN9T8rBtrAArYIUruBJXkFheCQYlCpQP6uk5dAQFQNaUROMSGVQFxLmkoQsxDJrhLbTZ+nvVsERME9MgPJRKV/58AsyomTSzE813WLFvWK++qI0xSfQl8k8Pg46sYRuv5t6dS+4RqxDwaa4BGjYH+NTQvKScIp9+YL/hoZh3jDtLRHtt2C3g6bmhX+CpsFBWg7ilDSPgj0lD2ncr5ev/BP8VvyAJhqVyZeUhPOrEhEFxgEtjft846Z/guQTNT89Q5P9flMLoth4F7808wKtWWKzAwNQHxrh/1vaid2F+XpYTSbQf1XA2McOmOpROnvpvMEA4tSjq1cW0sws2gCYxswY6TKkvzYnJq1NHZLnRU4BX+4U0uburvusu8Kv8iHY7qefkM4IFngJHEOUXmLEPgiGsI8YnlZILit3vSSLRTQe/MPIZva5pshNIEmyFQlCvruJKXPkCEfmePzkphXHdzZNQdoRI9KPlBAxlj/I8U97ERPS5bjGbWDFbEdqHVe5caTBeZZx2H/IMvzeN15yoQAAAABJRU5ErkJggg==

View file

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path fill="#FFF" d="M1703 478q40 57 18 129l-275 906q-19 64-76.5 107.5T1247 1664H324q-77 0-148.5-53.5T76 1479q-24-67-2-127 0-4 3-27t4-37q1-8-3-21.5t-3-19.5q2-11 8-21t16.5-23.5T116 1179q23-38 45-91.5t30-91.5q3-10 .5-30t-.5-28q3-11 17-28t17-23q21-36 42-92t25-90q1-9-2.5-32t.5-28q4-13 22-30.5t22-22.5q19-26 42.5-84.5T404 411q1-8-3-25.5t-2-26.5q2-8 9-18t18-23 17-21q8-12 16.5-30.5t15-35 16-36 19.5-32 26.5-23.5 36-11.5T620 134l-1 3q38-9 51-9h761q74 0 114 56t18 130l-274 906q-36 119-71.5 153.5T1089 1408H220q-27 0-38 15-11 16-1 43 24 70 144 70h923q29 0 56-15.5t35-41.5l300-987q7-22 5-57 38 15 59 43zm-1064 2q-4 13 2 22.5t20 9.5h608q13 0 25.5-9.5T1311 480l21-64q4-13-2-22.5t-20-9.5H702q-13 0-25.5 9.5T660 416zm-83 256q-4 13 2 22.5t20 9.5h608q13 0 25.5-9.5T1228 736l21-64q4-13-2-22.5t-20-9.5H619q-13 0-25.5 9.5T577 672z"/></svg>

After

Width:  |  Height:  |  Size: 913 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>

After

Width:  |  Height:  |  Size: 265 B

View file

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1344 800v64q0 14-9 23t-23 9H480q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h832q14 0 23 9t9 23zm128 448V416q0-66-47-113t-113-47H480q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113zm128-832v832q0 119-84.5 203.5T1312 1536H480q-119 0-203.5-84.5T192 1248V416q0-119 84.5-203.5T480 128h832q119 0 203.5 84.5T1600 416z"/></svg>

After

Width:  |  Height:  |  Size: 432 B

View file

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1408 960V832q0-26-19-45t-45-19H448q-26 0-45 19t-19 45v128q0 26 19 45t45 19h896q26 0 45-19t19-45zm256-544v960q0 119-84.5 203.5T1376 1664H416q-119 0-203.5-84.5T128 1376V416q0-119 84.5-203.5T416 128h960q119 0 203.5 84.5T1664 416z"/></svg>

After

Width:  |  Height:  |  Size: 337 B

View file

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1344 800v64q0 14-9 23t-23 9H960v352q0 14-9 23t-23 9h-64q-14 0-23-9t-9-23V896H480q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h352V416q0-14 9-23t23-9h64q14 0 23 9t9 23v352h352q14 0 23 9t9 23zm128 448V416q0-66-47-113t-113-47H480q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113zm128-832v832q0 119-84.5 203.5T1312 1536H480q-119 0-203.5-84.5T192 1248V416q0-119 84.5-203.5T480 128h832q119 0 203.5 84.5T1600 416z"/></svg>

After

Width:  |  Height:  |  Size: 526 B

View file

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1408 960V832q0-26-19-45t-45-19h-320V448q0-26-19-45t-45-19H832q-26 0-45 19t-19 45v320H448q-26 0-45 19t-19 45v128q0 26 19 45t45 19h320v320q0 26 19 45t45 19h128q26 0 45-19t19-45v-320h320q26 0 45-19t19-45zm256-544v960q0 119-84.5 203.5T1376 1664H416q-119 0-203.5-84.5T128 1376V416q0-119 84.5-203.5T416 128h960q119 0 203.5 84.5T1664 416z"/></svg>

After

Width:  |  Height:  |  Size: 442 B

View file

@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path fill="#FFF" d="M896 0q182 0 348 71t286 191 191 286 71 348-71 348-191 286-286 191-348 71-348-71-286-191-191-286T0 896t71-348 191-286T548 71 896 0zm0 128q-190 0-361 90l194 194q82-28 167-28t167 28l194-194q-171-90-361-90zM218 1257l194-194q-28-82-28-167t28-167L218 535q-90 171-90 361t90 361zm678 407q190 0 361-90l-194-194q-82 28-167 28t-167-28l-194 194q171 90 361 90zm0-384q159 0 271.5-112.5T1280 896t-112.5-271.5T896 512 624.5 624.5 512 896t112.5 271.5T896 1280zm484-217l194 194q90-171 90-361t-90-361l-194 194q28 82 28 167t-28 167z"/></svg>

After

Width:  |  Height:  |  Size: 634 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#FFF" d="M12 .9C5.8.9.9 5.8.9 12a11 11 0 1 0 22.2 0A11 11 0 0 0 12 .9zm6.5 6c-.6 0-.9-.3-.9-.8 0-.2 0-.4.2-.6l.2-.4c0-.3-.5-.4-.6-.4-1.8.1-2.3 2.5-2.7 4.4l-.2 1c1 .2 1.8 0 2.2-.3.6-.4-.2-.7-.1-1.2.1-.3.5-.5.7-.6.5 0 .7.5.7.9 0 .7-1 1.8-3 1.8l-.6-.1-.6 2.4c-.4 1.6-.8 3.8-2.4 5.7-1.4 1.7-2.9 1.9-3.5 1.9-1.2 0-1.9-.6-2-1.5 0-.8.7-1.3 1.2-1.3.6 0 1.1.5 1.1 1s-.2.6-.4.6c-.1.1-.3.2-.3.4 0 .1.1.3.4.3.5 0 .8-.3 1.1-.5 1.2-.9 1.6-2.7 2.2-5.7l.1-.7.7-3.2c-.8-.6-1.3-1.4-2.4-1.7-.6-.1-1.1.1-1.5.5-.4.5-.2 1.1.2 1.5l.7.6c.7.8 1.2 1.6 1 2.5-.3 1.5-2 2.6-4 1.9-1.8-.6-2-1.8-1.8-2.5.2-.6.6-.7 1.1-.6.5.2.6.7.6 1.2l-.1.3c-.2.1-.3.3-.3.4-.1.4.4.6.7.7.7.3 1.6-.2 1.8-.8a1 1 0 0 0-.4-1.1l-.7-.8c-.4-.4-1.1-1.4-.7-2.6.1-.5.4-.9.7-1.3a4 4 0 0 1 2.8-.6c1.2.4 1.8 1.1 2.6 1.8.5-1.2 1-2.4 1.8-3.5.9-.9 1.9-1.6 3.1-1.7 1.3.2 2.2.7 2.2 1.6 0 .4-.2 1.1-.9 1.1z"/></svg>

After

Width:  |  Height:  |  Size: 942 B

View file

@ -0,0 +1,285 @@
/* This file is based on WebProfilerBundle/Resources/views/Profiler/base_js.html.twig.
If you make any change in this file, verify the same change is needed in the other file. */
/*<![CDATA[*/
(function() {
"use strict";
if ('classList' in document.documentElement) {
var hasClass = function (el, cssClass) { return el.classList.contains(cssClass); };
var removeClass = function(el, cssClass) { el.classList.remove(cssClass); };
var addClass = function(el, cssClass) { el.classList.add(cssClass); };
var toggleClass = function(el, cssClass) { el.classList.toggle(cssClass); };
} else {
var hasClass = function (el, cssClass) { return el.className.match(new RegExp('\\b' + cssClass + '\\b')); };
var removeClass = function(el, cssClass) { el.className = el.className.replace(new RegExp('\\b' + cssClass + '\\b'), ' '); };
var addClass = function(el, cssClass) { if (!hasClass(el, cssClass)) { el.className += " " + cssClass; } };
var toggleClass = function(el, cssClass) { hasClass(el, cssClass) ? removeClass(el, cssClass) : addClass(el, cssClass); };
}
var addEventListener;
var el = document.createElement('div');
if (!('addEventListener' in el)) {
addEventListener = function (element, eventName, callback) {
element.attachEvent('on' + eventName, callback);
};
} else {
addEventListener = function (element, eventName, callback) {
element.addEventListener(eventName, callback, false);
};
}
if (navigator.clipboard) {
document.querySelectorAll('[data-clipboard-text]').forEach(function(element) {
removeClass(element, 'hidden');
element.addEventListener('click', function() {
navigator.clipboard.writeText(element.getAttribute('data-clipboard-text'));
})
});
}
(function createTabs() {
var tabGroups = document.querySelectorAll('.sf-tabs:not([data-processed=true])');
/* create the tab navigation for each group of tabs */
for (var i = 0; i < tabGroups.length; i++) {
var tabs = tabGroups[i].querySelectorAll(':scope > .tab');
var tabNavigation = document.createElement('ul');
tabNavigation.className = 'tab-navigation';
var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */
for (var j = 0; j < tabs.length; j++) {
var tabId = 'tab-' + i + '-' + j;
var tabTitle = tabs[j].querySelector('.tab-title').innerHTML;
var tabNavigationItem = document.createElement('li');
tabNavigationItem.setAttribute('data-tab-id', tabId);
if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; }
if (hasClass(tabs[j], 'disabled')) { addClass(tabNavigationItem, 'disabled'); }
tabNavigationItem.innerHTML = tabTitle;
tabNavigation.appendChild(tabNavigationItem);
var tabContent = tabs[j].querySelector('.tab-content');
tabContent.parentElement.setAttribute('id', tabId);
}
tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild);
addClass(document.querySelector('[data-tab-id="' + selectedTabId + '"]'), 'active');
}
/* display the active tab and add the 'click' event listeners */
for (i = 0; i < tabGroups.length; i++) {
tabNavigation = tabGroups[i].querySelectorAll(':scope >.tab-navigation li');
for (j = 0; j < tabNavigation.length; j++) {
tabId = tabNavigation[j].getAttribute('data-tab-id');
document.getElementById(tabId).querySelector('.tab-title').className = 'hidden';
if (hasClass(tabNavigation[j], 'active')) {
document.getElementById(tabId).className = 'block';
} else {
document.getElementById(tabId).className = 'hidden';
}
tabNavigation[j].addEventListener('click', function(e) {
var activeTab = e.target || e.srcElement;
/* needed because when the tab contains HTML contents, user can click */
/* on any of those elements instead of their parent '<li>' element */
while (activeTab.tagName.toLowerCase() !== 'li') {
activeTab = activeTab.parentNode;
}
/* get the full list of tabs through the parent of the active tab element */
var tabNavigation = activeTab.parentNode.children;
for (var k = 0; k < tabNavigation.length; k++) {
var tabId = tabNavigation[k].getAttribute('data-tab-id');
document.getElementById(tabId).className = 'hidden';
removeClass(tabNavigation[k], 'active');
}
addClass(activeTab, 'active');
var activeTabId = activeTab.getAttribute('data-tab-id');
document.getElementById(activeTabId).className = 'block';
});
}
tabGroups[i].setAttribute('data-processed', 'true');
}
})();
(function createToggles() {
var toggles = document.querySelectorAll('.sf-toggle:not([data-processed=true])');
for (var i = 0; i < toggles.length; i++) {
var elementSelector = toggles[i].getAttribute('data-toggle-selector');
var element = document.querySelector(elementSelector);
addClass(element, 'sf-toggle-content');
if (toggles[i].hasAttribute('data-toggle-initial') && toggles[i].getAttribute('data-toggle-initial') == 'display') {
addClass(toggles[i], 'sf-toggle-on');
addClass(element, 'sf-toggle-visible');
} else {
addClass(toggles[i], 'sf-toggle-off');
addClass(element, 'sf-toggle-hidden');
}
addEventListener(toggles[i], 'click', function(e) {
e.preventDefault();
if ('' !== window.getSelection().toString()) {
/* Don't do anything on text selection */
return;
}
var toggle = e.target || e.srcElement;
/* needed because when the toggle contains HTML contents, user can click */
/* on any of those elements instead of their parent '.sf-toggle' element */
while (!hasClass(toggle, 'sf-toggle')) {
toggle = toggle.parentNode;
}
var element = document.querySelector(toggle.getAttribute('data-toggle-selector'));
toggleClass(toggle, 'sf-toggle-on');
toggleClass(toggle, 'sf-toggle-off');
toggleClass(element, 'sf-toggle-hidden');
toggleClass(element, 'sf-toggle-visible');
/* the toggle doesn't change its contents when clicking on it */
if (!toggle.hasAttribute('data-toggle-alt-content')) {
return;
}
if (!toggle.hasAttribute('data-toggle-original-content')) {
toggle.setAttribute('data-toggle-original-content', toggle.innerHTML);
}
var currentContent = toggle.innerHTML;
var originalContent = toggle.getAttribute('data-toggle-original-content');
var altContent = toggle.getAttribute('data-toggle-alt-content');
toggle.innerHTML = currentContent !== altContent ? altContent : originalContent;
});
/* Prevents from disallowing clicks on links inside toggles */
var toggleLinks = toggles[i].querySelectorAll('a');
for (var j = 0; j < toggleLinks.length; j++) {
addEventListener(toggleLinks[j], 'click', function(e) {
e.stopPropagation();
});
}
/* Prevents from disallowing clicks on "copy to clipboard" elements inside toggles */
var copyToClipboardElements = toggles[i].querySelectorAll('span[data-clipboard-text]');
for (var k = 0; k < copyToClipboardElements.length; k++) {
addEventListener(copyToClipboardElements[k], 'click', function(e) {
e.stopPropagation();
});
}
toggles[i].setAttribute('data-processed', 'true');
}
})();
(function createFilters() {
document.querySelectorAll('[data-filters] [data-filter]').forEach(function (filter) {
var filters = filter.closest('[data-filters]'),
type = 'choice',
name = filter.dataset.filter,
ucName = name.charAt(0).toUpperCase()+name.slice(1),
list = document.createElement('ul'),
values = filters.dataset['filter'+ucName] || filters.querySelectorAll('[data-filter-'+name+']'),
labels = {},
defaults = null,
indexed = {},
processed = {};
if (typeof values === 'string') {
type = 'level';
labels = values.split(',');
values = values.toLowerCase().split(',');
defaults = values.length - 1;
}
addClass(list, 'filter-list');
addClass(list, 'filter-list-'+type);
values.forEach(function (value, i) {
if (value instanceof HTMLElement) {
value = value.dataset['filter'+ucName];
}
if (value in processed) {
return;
}
var option = document.createElement('li'),
label = i in labels ? labels[i] : value,
active = false,
matches;
if ('' === label) {
option.innerHTML = '<em>(none)</em>';
} else {
option.innerText = label;
}
option.dataset.filter = value;
option.setAttribute('title', 1 === (matches = filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').length) ? 'Matches 1 row' : 'Matches '+matches+' rows');
indexed[value] = i;
list.appendChild(option);
addEventListener(option, 'click', function () {
if ('choice' === type) {
filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
if (option.dataset.filter === row.dataset['filter'+ucName]) {
toggleClass(row, 'filter-hidden-'+name);
}
});
toggleClass(option, 'active');
} else if ('level' === type) {
if (i === this.parentNode.querySelectorAll('.active').length - 1) {
return;
}
this.parentNode.querySelectorAll('li').forEach(function (currentOption, j) {
if (j <= i) {
addClass(currentOption, 'active');
if (i === j) {
addClass(currentOption, 'last-active');
} else {
removeClass(currentOption, 'last-active');
}
} else {
removeClass(currentOption, 'active');
removeClass(currentOption, 'last-active');
}
});
filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
if (i < indexed[row.dataset['filter'+ucName]]) {
addClass(row, 'filter-hidden-'+name);
} else {
removeClass(row, 'filter-hidden-'+name);
}
});
}
});
if ('choice' === type) {
active = null === defaults || 0 <= defaults.indexOf(value);
} else if ('level' === type) {
active = i <= defaults;
if (active && i === defaults) {
addClass(option, 'last-active');
}
}
if (active) {
addClass(option, 'active');
} else {
filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').forEach(function (row) {
toggleClass(row, 'filter-hidden-'+name);
});
}
processed[value] = true;
});
if (1 < list.childNodes.length) {
filter.appendChild(list);
filter.dataset.filtered = '';
}
});
})();
})();
/*]]>*/

View file

@ -0,0 +1,84 @@
#!/usr/bin/env php
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if ('cli' !== \PHP_SAPI) {
throw new Exception('This script must be run from the command line.');
}
// Run from the root of the php-src repository, this script generates
// a table with all the methods that have a tentative return type.
//
// Usage: find -name *.stub.php | sort | /path/to/extract-tentative-return-types.php > /path/to/TentativeTypes.php
echo <<<EOPHP
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler\Internal;
/**
* This class has been generated by extract-tentative-return-types.php.
*
* @internal
*/
class TentativeTypes
{
public const RETURN_TYPES = [
EOPHP;
while (false !== $file = fgets(\STDIN)) {
$code = file_get_contents(substr($file, 0, -1));
if (!str_contains($code, '@tentative-return-type')) {
continue;
}
$code = preg_split('{^\s*(?:(?:abstract )?class|interface|trait) ([^\s]++)}m', $code, -1, \PREG_SPLIT_DELIM_CAPTURE);
if (1 === count($code)) {
continue;
}
for ($i = 1; null !== $class = $code[$i] ?? null; $i += 2) {
$methods = $code[1 + $i];
if (!str_contains($methods, '@tentative-return-type')) {
continue;
}
echo " '$class' => [\n";
preg_replace_callback('{@tentative-return-type.*?[\s]function ([^(]++)[^)]++\)\s*+:\s*+([^\n;\{]++)}s', function ($m) {
$m[2] = str_replace(' ', '', $m[2]);
echo " '$m[1]' => '$m[2]',\n";
return '';
}, $methods);
echo " ],\n";
}
}
echo <<<EOPHP
];
}
EOPHP;

View file

@ -0,0 +1,98 @@
#!/usr/bin/env php
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if ('cli' !== \PHP_SAPI) {
throw new Exception('This script must be run from the command line.');
}
if (\in_array('-h', $argv) || \in_array('--help', $argv)) {
echo implode(PHP_EOL, [
' Patches type declarations based on "@return" PHPDoc and triggers deprecations for',
' incompatible method declarations.',
'',
' This assists you to make your package compatible with Symfony 6, but it can be used',
' for any class/package.',
'',
' Available configuration via environment variables:',
' SYMFONY_PATCH_TYPE_DECLARATIONS',
' An url-encoded string to change the behavior of the script. Available parameters:',
' - "force": any value enables deprecation notices - can be any of:',
' - "phpdoc" to patch only docblock annotations',
' - "2" to add all possible return types',
' - "1" to add return types but only to tests/final/internal/private methods',
' - "php": the target version of PHP - e.g. "7.1" doesn\'t generate "object" types',
' - "deprecations": "1" to trigger a deprecation notice when a child class misses a',
' return type while the parent declares an "@return" annotation',
'',
' SYMFONY_PATCH_TYPE_EXCLUDE',
' A regex matched against the full path to the class - any match will be excluded',
'',
' Example: "SYMFONY_PATCH_TYPE_DECLARATIONS=php=7.4 ./patch-type-declarations"',
]);
exit;
}
if (false === getenv('SYMFONY_PATCH_TYPE_DECLARATIONS')) {
putenv('SYMFONY_PATCH_TYPE_DECLARATIONS=force=2');
echo 'No SYMFONY_PATCH_TYPE_DECLARATIONS env var set, patching type declarations in all methods (run the command with "-h" for more information).'.PHP_EOL;
}
if (is_file($autoload = __DIR__.'/../../../../autoload.php')) {
// noop
} elseif (is_file($autoload = __DIR__.'/../../../../../../../autoload.php')) {
// noop
} else {
echo PHP_EOL.' /!\ Cannot find the Composer autoloader, did you forget to run "composer install"?'.PHP_EOL;
exit(1);
}
if (is_file($phpunitAutoload = dirname($autoload).'/bin/.phpunit/phpunit/vendor/autoload.php')) {
require $phpunitAutoload;
}
$loader = require $autoload;
Symfony\Component\ErrorHandler\DebugClassLoader::enable();
$deprecations = [];
set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$deprecations) {
if (\E_USER_DEPRECATED !== $type) {
return;
}
[,,,,, $class,] = explode('"', $msg);
$deprecations[$class][] = $msg;
});
$exclude = getenv('SYMFONY_PATCH_TYPE_EXCLUDE') ?: null;
foreach ($loader->getClassMap() as $class => $file) {
if (false !== strpos($file = realpath($file), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) {
continue;
}
if ($exclude && preg_match($exclude, $file)) {
continue;
}
class_exists($class);
}
Symfony\Component\ErrorHandler\DebugClassLoader::checkClasses();
foreach ($deprecations as $class => $classDeprecations) {
echo $class.' ('.\count($classDeprecations).')'.PHP_EOL;
echo implode(PHP_EOL, $classDeprecations).PHP_EOL.PHP_EOL;
}
if ($deprecations && false !== strpos(getenv('SYMFONY_PATCH_TYPE_DECLARATIONS') ?? '', 'force')) {
echo 'These deprecations might be fixed by the patch script, run this again to check for type deprecations.'.PHP_EOL;
}

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="<?= $this->charset; ?>" />
<meta name="robots" content="noindex,nofollow,noarchive" />
<title>An Error Occurred: <?= $statusText; ?></title>
<style><?= $this->include('assets/css/error.css'); ?></style>
</head>
<body>
<div class="container">
<h1>Oops! An Error Occurred</h1>
<h2>The server returned a "<?= $statusCode; ?> <?= $statusText; ?>".</h2>
<p>
Something is broken. Please let us know what you were doing when this error occurred.
We will fix it as soon as possible. Sorry for any inconvenience caused.
</p>
</div>
</body>
</html>

View file

@ -0,0 +1,116 @@
<div class="exception-summary <?= !$exceptionMessage ? 'exception-without-message' : ''; ?>">
<div class="exception-metadata">
<div class="container">
<h2 class="exception-hierarchy">
<?php foreach (array_reverse($exception->getAllPrevious(), true) as $index => $previousException) { ?>
<a href="#trace-box-<?= $index + 2; ?>"><?= $this->abbrClass($previousException->getClass()); ?></a>
<span class="icon"><?= $this->include('assets/images/chevron-right.svg'); ?></span>
<?php } ?>
<a href="#trace-box-1"><?= $this->abbrClass($exception->getClass()); ?></a>
</h2>
<h2 class="exception-http">
HTTP <?= $statusCode; ?> <small><?= $statusText; ?></small>
</h2>
</div>
</div>
<div class="exception-message-wrapper">
<div class="container">
<h1 class="break-long-words exception-message<?= mb_strlen($exceptionMessage) > 180 ? ' long' : ''; ?>"><?= $this->formatFileFromText(nl2br($exceptionMessage)); ?></h1>
<div class="exception-illustration hidden-xs-down">
<?= $this->include('assets/images/symfony-ghost.svg.php'); ?>
</div>
</div>
</div>
</div>
<div class="container">
<div class="sf-tabs">
<div class="tab">
<?php
$exceptionAsArray = $exception->toArray();
$exceptionWithUserCode = [];
$exceptionAsArrayCount = count($exceptionAsArray);
$last = $exceptionAsArrayCount - 1;
foreach ($exceptionAsArray as $i => $e) {
foreach ($e['trace'] as $trace) {
if ($trace['file'] && false === mb_strpos($trace['file'], '/vendor/') && false === mb_strpos($trace['file'], '/var/cache/') && $i < $last) {
$exceptionWithUserCode[] = $i;
}
}
}
?>
<h3 class="tab-title">
<?php if ($exceptionAsArrayCount > 1) { ?>
Exceptions <span class="badge"><?= $exceptionAsArrayCount; ?></span>
<?php } else { ?>
Exception
<?php } ?>
</h3>
<div class="tab-content">
<?php
foreach ($exceptionAsArray as $i => $e) {
echo $this->include('views/traces.html.php', [
'exception' => $e,
'index' => $i + 1,
'expand' => in_array($i, $exceptionWithUserCode, true) || ([] === $exceptionWithUserCode && 0 === $i),
]);
}
?>
</div>
</div>
<?php if ($logger) { ?>
<div class="tab <?= !$logger->getLogs() ? 'disabled' : ''; ?>">
<h3 class="tab-title">
Logs
<?php if ($logger->countErrors()) { ?><span class="badge status-error"><?= $logger->countErrors(); ?></span><?php } ?>
</h3>
<div class="tab-content">
<?php if ($logger->getLogs()) { ?>
<?= $this->include('views/logs.html.php', ['logs' => $logger->getLogs()]); ?>
<?php } else { ?>
<div class="empty">
<p>No log messages</p>
</div>
<?php } ?>
</div>
</div>
<?php } ?>
<div class="tab">
<h3 class="tab-title">
<?php if ($exceptionAsArrayCount > 1) { ?>
Stack Traces <span class="badge"><?= $exceptionAsArrayCount; ?></span>
<?php } else { ?>
Stack Trace
<?php } ?>
</h3>
<div class="tab-content">
<?php
foreach ($exceptionAsArray as $i => $e) {
echo $this->include('views/traces_text.html.php', [
'exception' => $e,
'index' => $i + 1,
'numExceptions' => $exceptionAsArrayCount,
]);
}
?>
</div>
</div>
<?php if ($currentContent) { ?>
<div class="tab">
<h3 class="tab-title">Output content</h3>
<div class="tab-content">
<?= $currentContent; ?>
</div>
</div>
<?php } ?>
</div>
</div>

View file

@ -0,0 +1,42 @@
<!-- <?= $_message = sprintf('%s (%d %s)', $exceptionMessage, $statusCode, $statusText); ?> -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="<?= $this->charset; ?>" />
<meta name="robots" content="noindex,nofollow" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title><?= $_message; ?></title>
<link rel="icon" type="image/png" href="<?= $this->include('assets/images/favicon.png.base64'); ?>">
<style><?= $this->include('assets/css/exception.css'); ?></style>
<style><?= $this->include('assets/css/exception_full.css'); ?></style>
</head>
<body>
<script>
document.body.classList.add(
localStorage.getItem('symfony/profiler/theme') || (matchMedia('(prefers-color-scheme: dark)').matches ? 'theme-dark' : 'theme-light')
);
</script>
<?php if (class_exists(\Symfony\Component\HttpKernel\Kernel::class)) { ?>
<header>
<div class="container">
<h1 class="logo"><?= $this->include('assets/images/symfony-logo.svg'); ?> Symfony Exception</h1>
<div class="help-link">
<a href="https://symfony.com/doc/<?= Symfony\Component\HttpKernel\Kernel::VERSION; ?>/index.html">
<span class="icon"><?= $this->include('assets/images/icon-book.svg'); ?></span>
<span class="hidden-xs-down">Symfony</span> Docs
</a>
</div>
</div>
</header>
<?php } ?>
<?= $this->include('views/exception.html.php', $context); ?>
<script>
<?= $this->include('assets/js/exception.js'); ?>
</script>
</body>
</html>
<!-- <?= $_message; ?> -->

View file

@ -0,0 +1,45 @@
<table class="logs" data-filter-level="Emergency,Alert,Critical,Error,Warning,Notice,Info,Debug" data-filters>
<?php $channelIsDefined = isset($logs[0]['channel']); ?>
<thead>
<tr>
<th data-filter="level">Level</th>
<?php if ($channelIsDefined) { ?><th data-filter="channel">Channel</th><?php } ?>
<th class="full-width">Message</th>
</tr>
</thead>
<tbody>
<?php
foreach ($logs as $log) {
if ($log['priority'] >= 400) {
$status = 'error';
} elseif ($log['priority'] >= 300) {
$status = 'warning';
} else {
$severity = 0;
if (($exception = $log['context']['exception'] ?? null) instanceof \ErrorException || $exception instanceof \Symfony\Component\ErrorHandler\Exception\SilencedErrorContext) {
$severity = $exception->getSeverity();
}
$status = \E_DEPRECATED === $severity || \E_USER_DEPRECATED === $severity ? 'warning' : 'normal';
} ?>
<tr class="status-<?= $status; ?>" data-filter-level="<?= strtolower($this->escape($log['priorityName'])); ?>"<?php if ($channelIsDefined) { ?> data-filter-channel="<?= $this->escape($log['channel']); ?>"<?php } ?>>
<td class="text-small nowrap">
<span class="colored text-bold"><?= $this->escape($log['priorityName']); ?></span>
<span class="text-muted newline"><?= date('H:i:s', $log['timestamp']); ?></span>
</td>
<?php if ($channelIsDefined) { ?>
<td class="text-small text-bold nowrap">
<?= $this->escape($log['channel']); ?>
</td>
<?php } ?>
<td>
<?= $this->formatLogMessage($log['message'], $log['context']); ?>
<?php if ($log['context']) { ?>
<pre class="text-muted prewrap m-t-5"><?= $this->escape(json_encode($log['context'], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)); ?></pre>
<?php } ?>
</td>
</tr>
<?php
} ?>
</tbody>
</table>

View file

@ -0,0 +1,43 @@
<div class="trace-line-header break-long-words <?= $trace['file'] ? 'sf-toggle' : ''; ?>" data-toggle-selector="#trace-html-<?= $prefix; ?>-<?= $i; ?>" data-toggle-initial="<?= 'expanded' === $style ? 'display' : ''; ?>">
<?php if ($trace['file']) { ?>
<span class="icon icon-close"><?= $this->include('assets/images/icon-minus-square.svg'); ?></span>
<span class="icon icon-open"><?= $this->include('assets/images/icon-plus-square.svg'); ?></span>
<?php } ?>
<?php if ('compact' !== $style && $trace['function']) { ?>
<span class="trace-class"><?= $this->abbrClass($trace['class']); ?></span><?php if ($trace['type']) { ?><span class="trace-type"><?= $trace['type']; ?></span><?php } ?><span class="trace-method"><?= $trace['function']; ?></span><?php if (isset($trace['args'])) { ?><span class="trace-arguments">(<?= $this->formatArgs($trace['args']); ?>)</span><?php } ?>
<?php } ?>
<?php if ($trace['file']) { ?>
<?php
$lineNumber = $trace['line'] ?: 1;
$fileLink = $this->getFileLink($trace['file'], $lineNumber);
$filePath = strtr(strip_tags($this->formatFile($trace['file'], $lineNumber)), [' at line '.$lineNumber => '']);
$filePathParts = explode(\DIRECTORY_SEPARATOR, $filePath);
?>
<span class="block trace-file-path">
in
<a href="<?= $fileLink; ?>">
<?= implode(\DIRECTORY_SEPARATOR, array_slice($filePathParts, 0, -1)).\DIRECTORY_SEPARATOR; ?><strong><?= end($filePathParts); ?></strong>
</a>
<?php if ('compact' === $style && $trace['function']) { ?>
<span class="trace-type"><?= $trace['type']; ?></span>
<span class="trace-method"><?= $trace['function']; ?></span>
<?php } ?>
(line <?= $lineNumber; ?>)
<span class="icon icon-copy hidden" data-clipboard-text="<?php echo implode(\DIRECTORY_SEPARATOR, $filePathParts).':'.$lineNumber; ?>">
<?php echo $this->include('assets/images/icon-copy.svg'); ?>
</span>
</span>
<?php } ?>
</div>
<?php if ($trace['file']) { ?>
<div id="trace-html-<?= $prefix.'-'.$i; ?>" class="trace-code sf-toggle-content">
<?= strtr($this->fileExcerpt($trace['file'], $trace['line'], 5), [
'#DD0000' => 'var(--highlight-string)',
'#007700' => 'var(--highlight-keyword)',
'#0000BB' => 'var(--highlight-default)',
'#FF8000' => 'var(--highlight-comment)',
]); ?>
</div>
<?php } ?>

View file

@ -0,0 +1,51 @@
<div class="trace trace-as-html" id="trace-box-<?= $index; ?>">
<div class="trace-details">
<div class="trace-head">
<div class="sf-toggle" data-toggle-selector="#trace-html-<?= $index; ?>" data-toggle-initial="<?= $expand ? 'display' : ''; ?>">
<span class="icon icon-close"><?= $this->include('assets/images/icon-minus-square-o.svg'); ?></span>
<span class="icon icon-open"><?= $this->include('assets/images/icon-plus-square-o.svg'); ?></span>
<?php
$separator = strrpos($exception['class'], '\\');
$separator = false === $separator ? 0 : $separator + 1;
$namespace = substr($exception['class'], 0, $separator);
$class = substr($exception['class'], $separator);
?>
<?php if ('' === $class) { ?>
<br>
<?php } else { ?>
<h3 class="trace-class">
<?php if ('' !== $namespace) { ?>
<span class="trace-namespace"><?= $namespace; ?></span>
<?php } ?>
<?= $class; ?>
</h3>
<?php } ?>
<?php if ($exception['message'] && $index > 1) { ?>
<p class="break-long-words trace-message"><?= $this->escape($exception['message']); ?></p>
<?php } ?>
</div>
</div>
<div id="trace-html-<?= $index; ?>" class="sf-toggle-content">
<?php
$isFirstUserCode = true;
foreach ($exception['trace'] as $i => $trace) {
$isVendorTrace = $trace['file'] && (false !== mb_strpos($trace['file'], '/vendor/') || false !== mb_strpos($trace['file'], '/var/cache/'));
$displayCodeSnippet = $isFirstUserCode && !$isVendorTrace;
if ($displayCodeSnippet) {
$isFirstUserCode = false;
} ?>
<div class="trace-line <?= $isVendorTrace ? 'trace-from-vendor' : ''; ?>">
<?= $this->include('views/trace.html.php', [
'prefix' => $index,
'i' => $i,
'trace' => $trace,
'style' => $isVendorTrace ? 'compact' : ($displayCodeSnippet ? 'expanded' : ''),
]); ?>
</div>
<?php
} ?>
</div>
</div>
</div>

View file

@ -0,0 +1,43 @@
<table class="trace trace-as-text">
<thead class="trace-head">
<tr>
<th class="sf-toggle" data-toggle-selector="#trace-text-<?= $index; ?>" data-toggle-initial="<?= 1 === $index ? 'display' : ''; ?>">
<div class="trace-class">
<?php if ($numExceptions > 1) { ?>
<span class="text-muted">[<?= $numExceptions - $index + 1; ?>/<?= $numExceptions; ?>]</span>
<?php } ?>
<?= ($parts = explode('\\', $exception['class'])) ? end($parts) : ''; ?>
<span class="icon icon-close"><?= $this->include('assets/images/icon-minus-square-o.svg'); ?></span>
<span class="icon icon-open"><?= $this->include('assets/images/icon-plus-square-o.svg'); ?></span>
</div>
</th>
</tr>
</thead>
<tbody id="trace-text-<?= $index; ?>">
<tr>
<td>
<?php if ($exception['trace']) { ?>
<pre class="stacktrace">
<?php
echo $this->escape($exception['class']).":\n";
if ($exception['message']) {
echo $this->escape($exception['message'])."\n";
}
foreach ($exception['trace'] as $trace) {
echo "\n ";
if ($trace['function']) {
echo $this->escape('at '.$trace['class'].$trace['type'].$trace['function']).'('.(isset($trace['args']) ? $this->formatArgsAsText($trace['args']) : '').')';
}
if ($trace['file'] && $trace['line']) {
echo($trace['function'] ? "\n (" : 'at ').strtr(strip_tags($this->formatFile($trace['file'], $trace['line'])), [' at line '.$trace['line'] => '']).':'.$trace['line'].($trace['function'] ? ')' : '');
}
}
?>
</pre>
<?php } ?>
</td>
</tr>
</tbody>
</table>

View file

@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ErrorHandler;
use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
/**
* @internal
*/
class ThrowableUtils
{
/**
* @param SilencedErrorContext|\Throwable
*/
public static function getSeverity($throwable): int
{
if ($throwable instanceof \ErrorException || $throwable instanceof SilencedErrorContext) {
return $throwable->getSeverity();
}
if ($throwable instanceof \ParseError) {
return \E_PARSE;
}
if ($throwable instanceof \TypeError) {
return \E_RECOVERABLE_ERROR;
}
return \E_ERROR;
}
}

View file

@ -0,0 +1,38 @@
{
"name": "symfony/error-handler",
"type": "library",
"description": "Provides tools to manage errors and ease debugging PHP code",
"keywords": [],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=7.2.5",
"psr/log": "^1|^2|^3",
"symfony/var-dumper": "^4.4|^5.0|^6.0"
},
"require-dev": {
"symfony/http-kernel": "^4.4|^5.0|^6.0",
"symfony/serializer": "^4.4|^5.0|^6.0",
"symfony/deprecation-contracts": "^2.1|^3"
},
"autoload": {
"psr-4": { "Symfony\\Component\\ErrorHandler\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"bin": [
"Resources/bin/patch-type-declarations"
],
"minimum-stability": "dev"
}

82
vendor/symfony/filesystem/CHANGELOG.md vendored Normal file
View file

@ -0,0 +1,82 @@
CHANGELOG
=========
5.4
---
* Add `Path` class
* Add `$lock` argument to `Filesystem::appendToFile()`
5.0.0
-----
* `Filesystem::dumpFile()` and `appendToFile()` don't accept arrays anymore
4.4.0
-----
* support for passing a `null` value to `Filesystem::isAbsolutePath()` is deprecated and will be removed in 5.0
* `tempnam()` now accepts a third argument `$suffix`.
4.3.0
-----
* support for passing arrays to `Filesystem::dumpFile()` is deprecated and will be removed in 5.0
* support for passing arrays to `Filesystem::appendToFile()` is deprecated and will be removed in 5.0
4.0.0
-----
* removed `LockHandler`
* Support for passing relative paths to `Filesystem::makePathRelative()` has been removed.
3.4.0
-----
* support for passing relative paths to `Filesystem::makePathRelative()` is deprecated and will be removed in 4.0
3.3.0
-----
* added `appendToFile()` to append contents to existing files
3.2.0
-----
* added `readlink()` as a platform independent method to read links
3.0.0
-----
* removed `$mode` argument from `Filesystem::dumpFile()`
2.8.0
-----
* added tempnam() a stream aware version of PHP's native tempnam()
2.6.0
-----
* added LockHandler
2.3.12
------
* deprecated dumpFile() file mode argument.
2.3.0
-----
* added the dumpFile() method to atomically write files
2.2.0
-----
* added a delete option for the mirror() method
2.1.0
-----
* 24eb396 : BC Break : mkdir() function now throws exception in case of failure instead of returning Boolean value
* created the component

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* Exception interface for all exceptions thrown by the component.
*
* @author Romain Neutron <imprec@gmail.com>
*/
interface ExceptionInterface extends \Throwable
{
}

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* Exception class thrown when a file couldn't be found.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
*/
class FileNotFoundException extends IOException
{
public function __construct(?string $message = null, int $code = 0, ?\Throwable $previous = null, ?string $path = null)
{
if (null === $message) {
if (null === $path) {
$message = 'File could not be found.';
} else {
$message = sprintf('File "%s" could not be found.', $path);
}
}
parent::__construct($message, $code, $previous, $path);
}
}

View file

@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* Exception class thrown when a filesystem operation failure happens.
*
* @author Romain Neutron <imprec@gmail.com>
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
class IOException extends \RuntimeException implements IOExceptionInterface
{
private $path;
public function __construct(string $message, int $code = 0, ?\Throwable $previous = null, ?string $path = null)
{
$this->path = $path;
parent::__construct($message, $code, $previous);
}
/**
* {@inheritdoc}
*/
public function getPath()
{
return $this->path;
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* IOException interface for file and input/output stream related exceptions thrown by the component.
*
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
*/
interface IOExceptionInterface extends ExceptionInterface
{
/**
* Returns the associated path for the exception.
*
* @return string|null
*/
public function getPath();
}

View file

@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}

View file

@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* @author Théo Fidry <theo.fidry@gmail.com>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}

788
vendor/symfony/filesystem/Filesystem.php vendored Normal file
View file

@ -0,0 +1,788 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem;
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
use Symfony\Component\Filesystem\Exception\InvalidArgumentException;
use Symfony\Component\Filesystem\Exception\IOException;
/**
* Provides basic utility to manipulate the file system.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Filesystem
{
private static $lastError;
/**
* Copies a file.
*
* If the target file is older than the origin file, it's always overwritten.
* If the target file is newer, it is overwritten only when the
* $overwriteNewerFiles option is set to true.
*
* @throws FileNotFoundException When originFile doesn't exist
* @throws IOException When copy fails
*/
public function copy(string $originFile, string $targetFile, bool $overwriteNewerFiles = false)
{
$originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://');
if ($originIsLocal && !is_file($originFile)) {
throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile);
}
$this->mkdir(\dirname($targetFile));
$doCopy = true;
if (!$overwriteNewerFiles && null === parse_url($originFile, \PHP_URL_HOST) && is_file($targetFile)) {
$doCopy = filemtime($originFile) > filemtime($targetFile);
}
if ($doCopy) {
// https://bugs.php.net/64634
if (!$source = self::box('fopen', $originFile, 'r')) {
throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading: ', $originFile, $targetFile).self::$lastError, 0, null, $originFile);
}
// Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
if (!$target = self::box('fopen', $targetFile, 'w', false, stream_context_create(['ftp' => ['overwrite' => true]]))) {
throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing: ', $originFile, $targetFile).self::$lastError, 0, null, $originFile);
}
$bytesCopied = stream_copy_to_stream($source, $target);
fclose($source);
fclose($target);
unset($source, $target);
if (!is_file($targetFile)) {
throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile);
}
if ($originIsLocal) {
// Like `cp`, preserve executable permission bits
self::box('chmod', $targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111));
// Like `cp`, preserve the file modification time
self::box('touch', $targetFile, filemtime($originFile));
if ($bytesCopied !== $bytesOrigin = filesize($originFile)) {
throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile);
}
}
}
}
/**
* Creates a directory recursively.
*
* @param string|iterable $dirs The directory path
*
* @throws IOException On any directory creation failure
*/
public function mkdir($dirs, int $mode = 0777)
{
foreach ($this->toIterable($dirs) as $dir) {
if (is_dir($dir)) {
continue;
}
if (!self::box('mkdir', $dir, $mode, true) && !is_dir($dir)) {
throw new IOException(sprintf('Failed to create "%s": ', $dir).self::$lastError, 0, null, $dir);
}
}
}
/**
* Checks the existence of files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to check
*
* @return bool
*/
public function exists($files)
{
$maxPathLength = \PHP_MAXPATHLEN - 2;
foreach ($this->toIterable($files) as $file) {
if (\strlen($file) > $maxPathLength) {
throw new IOException(sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file);
}
if (!file_exists($file)) {
return false;
}
}
return true;
}
/**
* Sets access and modification time of file.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to create
* @param int|null $time The touch time as a Unix timestamp, if not supplied the current system time is used
* @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used
*
* @throws IOException When touch fails
*/
public function touch($files, ?int $time = null, ?int $atime = null)
{
foreach ($this->toIterable($files) as $file) {
if (!($time ? self::box('touch', $file, $time, $atime) : self::box('touch', $file))) {
throw new IOException(sprintf('Failed to touch "%s": ', $file).self::$lastError, 0, null, $file);
}
}
}
/**
* Removes files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove
*
* @throws IOException When removal fails
*/
public function remove($files)
{
if ($files instanceof \Traversable) {
$files = iterator_to_array($files, false);
} elseif (!\is_array($files)) {
$files = [$files];
}
self::doRemove($files, false);
}
private static function doRemove(array $files, bool $isRecursive): void
{
$files = array_reverse($files);
foreach ($files as $file) {
if (is_link($file)) {
// See https://bugs.php.net/52176
if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) {
throw new IOException(sprintf('Failed to remove symlink "%s": ', $file).self::$lastError);
}
} elseif (is_dir($file)) {
if (!$isRecursive) {
$tmpName = \dirname(realpath($file)).'/.!'.strrev(strtr(base64_encode(random_bytes(2)), '/=', '-!'));
if (file_exists($tmpName)) {
try {
self::doRemove([$tmpName], true);
} catch (IOException $e) {
}
}
if (!file_exists($tmpName) && self::box('rename', $file, $tmpName)) {
$origFile = $file;
$file = $tmpName;
} else {
$origFile = null;
}
}
$files = new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS);
self::doRemove(iterator_to_array($files, true), true);
if (!self::box('rmdir', $file) && file_exists($file) && !$isRecursive) {
$lastError = self::$lastError;
if (null !== $origFile && self::box('rename', $file, $origFile)) {
$file = $origFile;
}
throw new IOException(sprintf('Failed to remove directory "%s": ', $file).$lastError);
}
} elseif (!self::box('unlink', $file) && ((self::$lastError && str_contains(self::$lastError, 'Permission denied')) || file_exists($file))) {
throw new IOException(sprintf('Failed to remove file "%s": ', $file).self::$lastError);
}
}
}
/**
* Change mode for an array of files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to change mode
* @param int $mode The new mode (octal)
* @param int $umask The mode mask (octal)
* @param bool $recursive Whether change the mod recursively or not
*
* @throws IOException When the change fails
*/
public function chmod($files, int $mode, int $umask = 0000, bool $recursive = false)
{
foreach ($this->toIterable($files) as $file) {
if ((\PHP_VERSION_ID < 80000 || \is_int($mode)) && !self::box('chmod', $file, $mode & ~$umask)) {
throw new IOException(sprintf('Failed to chmod file "%s": ', $file).self::$lastError, 0, null, $file);
}
if ($recursive && is_dir($file) && !is_link($file)) {
$this->chmod(new \FilesystemIterator($file), $mode, $umask, true);
}
}
}
/**
* Change the owner of an array of files or directories.
*
* This method always throws on Windows, as the underlying PHP function is not supported.
* @see https://www.php.net/chown
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to change owner
* @param string|int $user A user name or number
* @param bool $recursive Whether change the owner recursively or not
*
* @throws IOException When the change fails
*/
public function chown($files, $user, bool $recursive = false)
{
foreach ($this->toIterable($files) as $file) {
if ($recursive && is_dir($file) && !is_link($file)) {
$this->chown(new \FilesystemIterator($file), $user, true);
}
if (is_link($file) && \function_exists('lchown')) {
if (!self::box('lchown', $file, $user)) {
throw new IOException(sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file);
}
} else {
if (!self::box('chown', $file, $user)) {
throw new IOException(sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file);
}
}
}
}
/**
* Change the group of an array of files or directories.
*
* This method always throws on Windows, as the underlying PHP function is not supported.
* @see https://www.php.net/chgrp
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to change group
* @param string|int $group A group name or number
* @param bool $recursive Whether change the group recursively or not
*
* @throws IOException When the change fails
*/
public function chgrp($files, $group, bool $recursive = false)
{
foreach ($this->toIterable($files) as $file) {
if ($recursive && is_dir($file) && !is_link($file)) {
$this->chgrp(new \FilesystemIterator($file), $group, true);
}
if (is_link($file) && \function_exists('lchgrp')) {
if (!self::box('lchgrp', $file, $group)) {
throw new IOException(sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file);
}
} else {
if (!self::box('chgrp', $file, $group)) {
throw new IOException(sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file);
}
}
}
}
/**
* Renames a file or a directory.
*
* @throws IOException When target file or directory already exists
* @throws IOException When origin cannot be renamed
*/
public function rename(string $origin, string $target, bool $overwrite = false)
{
// we check that target does not exist
if (!$overwrite && $this->isReadable($target)) {
throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target);
}
if (!self::box('rename', $origin, $target)) {
if (is_dir($origin)) {
// See https://bugs.php.net/54097 & https://php.net/rename#113943
$this->mirror($origin, $target, null, ['override' => $overwrite, 'delete' => $overwrite]);
$this->remove($origin);
return;
}
throw new IOException(sprintf('Cannot rename "%s" to "%s": ', $origin, $target).self::$lastError, 0, null, $target);
}
}
/**
* Tells whether a file exists and is readable.
*
* @throws IOException When windows path is longer than 258 characters
*/
private function isReadable(string $filename): bool
{
$maxPathLength = \PHP_MAXPATHLEN - 2;
if (\strlen($filename) > $maxPathLength) {
throw new IOException(sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename);
}
return is_readable($filename);
}
/**
* Creates a symbolic link or copy a directory.
*
* @throws IOException When symlink fails
*/
public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false)
{
self::assertFunctionExists('symlink');
if ('\\' === \DIRECTORY_SEPARATOR) {
$originDir = strtr($originDir, '/', '\\');
$targetDir = strtr($targetDir, '/', '\\');
if ($copyOnWindows) {
$this->mirror($originDir, $targetDir);
return;
}
}
$this->mkdir(\dirname($targetDir));
if (is_link($targetDir)) {
if (readlink($targetDir) === $originDir) {
return;
}
$this->remove($targetDir);
}
if (!self::box('symlink', $originDir, $targetDir)) {
$this->linkException($originDir, $targetDir, 'symbolic');
}
}
/**
* Creates a hard link, or several hard links to a file.
*
* @param string|string[] $targetFiles The target file(s)
*
* @throws FileNotFoundException When original file is missing or not a file
* @throws IOException When link fails, including if link already exists
*/
public function hardlink(string $originFile, $targetFiles)
{
self::assertFunctionExists('link');
if (!$this->exists($originFile)) {
throw new FileNotFoundException(null, 0, null, $originFile);
}
if (!is_file($originFile)) {
throw new FileNotFoundException(sprintf('Origin file "%s" is not a file.', $originFile));
}
foreach ($this->toIterable($targetFiles) as $targetFile) {
if (is_file($targetFile)) {
if (fileinode($originFile) === fileinode($targetFile)) {
continue;
}
$this->remove($targetFile);
}
if (!self::box('link', $originFile, $targetFile)) {
$this->linkException($originFile, $targetFile, 'hard');
}
}
}
/**
* @param string $linkType Name of the link type, typically 'symbolic' or 'hard'
*/
private function linkException(string $origin, string $target, string $linkType)
{
if (self::$lastError) {
if ('\\' === \DIRECTORY_SEPARATOR && str_contains(self::$lastError, 'error code(1314)')) {
throw new IOException(sprintf('Unable to create "%s" link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target);
}
}
throw new IOException(sprintf('Failed to create "%s" link from "%s" to "%s": ', $linkType, $origin, $target).self::$lastError, 0, null, $target);
}
/**
* Resolves links in paths.
*
* With $canonicalize = false (default)
* - if $path does not exist or is not a link, returns null
* - if $path is a link, returns the next direct target of the link without considering the existence of the target
*
* With $canonicalize = true
* - if $path does not exist, returns null
* - if $path exists, returns its absolute fully resolved final version
*
* @return string|null
*/
public function readlink(string $path, bool $canonicalize = false)
{
if (!$canonicalize && !is_link($path)) {
return null;
}
if ($canonicalize) {
if (!$this->exists($path)) {
return null;
}
if ('\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70410) {
$path = readlink($path);
}
return realpath($path);
}
if ('\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70400) {
return realpath($path);
}
return readlink($path);
}
/**
* Given an existing path, convert it to a path relative to a given starting path.
*
* @return string
*/
public function makePathRelative(string $endPath, string $startPath)
{
if (!$this->isAbsolutePath($startPath)) {
throw new InvalidArgumentException(sprintf('The start path "%s" is not absolute.', $startPath));
}
if (!$this->isAbsolutePath($endPath)) {
throw new InvalidArgumentException(sprintf('The end path "%s" is not absolute.', $endPath));
}
// Normalize separators on Windows
if ('\\' === \DIRECTORY_SEPARATOR) {
$endPath = str_replace('\\', '/', $endPath);
$startPath = str_replace('\\', '/', $startPath);
}
$splitDriveLetter = function ($path) {
return (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0]))
? [substr($path, 2), strtoupper($path[0])]
: [$path, null];
};
$splitPath = function ($path) {
$result = [];
foreach (explode('/', trim($path, '/')) as $segment) {
if ('..' === $segment) {
array_pop($result);
} elseif ('.' !== $segment && '' !== $segment) {
$result[] = $segment;
}
}
return $result;
};
[$endPath, $endDriveLetter] = $splitDriveLetter($endPath);
[$startPath, $startDriveLetter] = $splitDriveLetter($startPath);
$startPathArr = $splitPath($startPath);
$endPathArr = $splitPath($endPath);
if ($endDriveLetter && $startDriveLetter && $endDriveLetter != $startDriveLetter) {
// End path is on another drive, so no relative path exists
return $endDriveLetter.':/'.($endPathArr ? implode('/', $endPathArr).'/' : '');
}
// Find for which directory the common path stops
$index = 0;
while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) {
++$index;
}
// Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
if (1 === \count($startPathArr) && '' === $startPathArr[0]) {
$depth = 0;
} else {
$depth = \count($startPathArr) - $index;
}
// Repeated "../" for each level need to reach the common path
$traverser = str_repeat('../', $depth);
$endPathRemainder = implode('/', \array_slice($endPathArr, $index));
// Construct $endPath from traversing to the common path, then to the remaining $endPath
$relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : '');
return '' === $relativePath ? './' : $relativePath;
}
/**
* Mirrors a directory to another.
*
* Copies files and directories from the origin directory into the target directory. By default:
*
* - existing files in the target directory will be overwritten, except if they are newer (see the `override` option)
* - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option)
*
* @param \Traversable|null $iterator Iterator that filters which files and directories to copy, if null a recursive iterator is created
* @param array $options An array of boolean options
* Valid options are:
* - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false)
* - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false)
* - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
*
* @throws IOException When file type is unknown
*/
public function mirror(string $originDir, string $targetDir, ?\Traversable $iterator = null, array $options = [])
{
$targetDir = rtrim($targetDir, '/\\');
$originDir = rtrim($originDir, '/\\');
$originDirLen = \strlen($originDir);
if (!$this->exists($originDir)) {
throw new IOException(sprintf('The origin directory specified "%s" was not found.', $originDir), 0, null, $originDir);
}
// Iterate in destination folder to remove obsolete entries
if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) {
$deleteIterator = $iterator;
if (null === $deleteIterator) {
$flags = \FilesystemIterator::SKIP_DOTS;
$deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST);
}
$targetDirLen = \strlen($targetDir);
foreach ($deleteIterator as $file) {
$origin = $originDir.substr($file->getPathname(), $targetDirLen);
if (!$this->exists($origin)) {
$this->remove($file);
}
}
}
$copyOnWindows = $options['copy_on_windows'] ?? false;
if (null === $iterator) {
$flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS;
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST);
}
$this->mkdir($targetDir);
$filesCreatedWhileMirroring = [];
foreach ($iterator as $file) {
if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset($filesCreatedWhileMirroring[$file->getRealPath()])) {
continue;
}
$target = $targetDir.substr($file->getPathname(), $originDirLen);
$filesCreatedWhileMirroring[$target] = true;
if (!$copyOnWindows && is_link($file)) {
$this->symlink($file->getLinkTarget(), $target);
} elseif (is_dir($file)) {
$this->mkdir($target);
} elseif (is_file($file)) {
$this->copy($file, $target, $options['override'] ?? false);
} else {
throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file);
}
}
}
/**
* Returns whether the file path is an absolute path.
*
* @return bool
*/
public function isAbsolutePath(string $file)
{
return '' !== $file && (strspn($file, '/\\', 0, 1)
|| (\strlen($file) > 3 && ctype_alpha($file[0])
&& ':' === $file[1]
&& strspn($file, '/\\', 2, 1)
)
|| null !== parse_url($file, \PHP_URL_SCHEME)
);
}
/**
* Creates a temporary file with support for custom stream wrappers.
*
* @param string $prefix The prefix of the generated temporary filename
* Note: Windows uses only the first three characters of prefix
* @param string $suffix The suffix of the generated temporary filename
*
* @return string The new temporary filename (with path), or throw an exception on failure
*/
public function tempnam(string $dir, string $prefix/* , string $suffix = '' */)
{
$suffix = \func_num_args() > 2 ? func_get_arg(2) : '';
[$scheme, $hierarchy] = $this->getSchemeAndHierarchy($dir);
// If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem
if ((null === $scheme || 'file' === $scheme || 'gs' === $scheme) && '' === $suffix) {
// If tempnam failed or no scheme return the filename otherwise prepend the scheme
if ($tmpFile = self::box('tempnam', $hierarchy, $prefix)) {
if (null !== $scheme && 'gs' !== $scheme) {
return $scheme.'://'.$tmpFile;
}
return $tmpFile;
}
throw new IOException('A temporary file could not be created: '.self::$lastError);
}
// Loop until we create a valid temp file or have reached 10 attempts
for ($i = 0; $i < 10; ++$i) {
// Create a unique filename
$tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true).$suffix;
// Use fopen instead of file_exists as some streams do not support stat
// Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability
if (!$handle = self::box('fopen', $tmpFile, 'x+')) {
continue;
}
// Close the file if it was successfully opened
self::box('fclose', $handle);
return $tmpFile;
}
throw new IOException('A temporary file could not be created: '.self::$lastError);
}
/**
* Atomically dumps content into a file.
*
* @param string|resource $content The data to write into the file
*
* @throws IOException if the file cannot be written to
*/
public function dumpFile(string $filename, $content)
{
if (\is_array($content)) {
throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__));
}
$dir = \dirname($filename);
if (is_link($filename) && $linkTarget = $this->readlink($filename)) {
$this->dumpFile(Path::makeAbsolute($linkTarget, $dir), $content);
return;
}
if (!is_dir($dir)) {
$this->mkdir($dir);
}
// Will create a temp file with 0600 access rights
// when the filesystem supports chmod.
$tmpFile = $this->tempnam($dir, basename($filename));
try {
if (false === self::box('file_put_contents', $tmpFile, $content)) {
throw new IOException(sprintf('Failed to write file "%s": ', $filename).self::$lastError, 0, null, $filename);
}
self::box('chmod', $tmpFile, self::box('fileperms', $filename) ?: 0666 & ~umask());
$this->rename($tmpFile, $filename, true);
} finally {
if (file_exists($tmpFile)) {
if ('\\' === \DIRECTORY_SEPARATOR && !is_writable($tmpFile)) {
self::box('chmod', $tmpFile, self::box('fileperms', $tmpFile) | 0200);
}
self::box('unlink', $tmpFile);
}
}
}
/**
* Appends content to an existing file.
*
* @param string|resource $content The content to append
* @param bool $lock Whether the file should be locked when writing to it
*
* @throws IOException If the file is not writable
*/
public function appendToFile(string $filename, $content/* , bool $lock = false */)
{
if (\is_array($content)) {
throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__));
}
$dir = \dirname($filename);
if (!is_dir($dir)) {
$this->mkdir($dir);
}
$lock = \func_num_args() > 2 && func_get_arg(2);
if (false === self::box('file_put_contents', $filename, $content, \FILE_APPEND | ($lock ? \LOCK_EX : 0))) {
throw new IOException(sprintf('Failed to write file "%s": ', $filename).self::$lastError, 0, null, $filename);
}
}
private function toIterable($files): iterable
{
return is_iterable($files) ? $files : [$files];
}
/**
* Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> [file, tmp]).
*/
private function getSchemeAndHierarchy(string $filename): array
{
$components = explode('://', $filename, 2);
return 2 === \count($components) ? [$components[0], $components[1]] : [null, $components[0]];
}
private static function assertFunctionExists(string $func): void
{
if (!\function_exists($func)) {
throw new IOException(sprintf('Unable to perform filesystem operation because the "%s()" function has been disabled.', $func));
}
}
/**
* @param mixed ...$args
*
* @return mixed
*/
private static function box(string $func, ...$args)
{
self::assertFunctionExists($func);
self::$lastError = null;
set_error_handler(__CLASS__.'::handleError');
try {
return $func(...$args);
} finally {
restore_error_handler();
}
}
/**
* @internal
*/
public static function handleError(int $type, string $msg)
{
self::$lastError = $msg;
}
}

19
vendor/symfony/filesystem/LICENSE vendored Normal file
View file

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

819
vendor/symfony/filesystem/Path.php vendored Normal file
View file

@ -0,0 +1,819 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem;
use Symfony\Component\Filesystem\Exception\InvalidArgumentException;
use Symfony\Component\Filesystem\Exception\RuntimeException;
/**
* Contains utility methods for handling path strings.
*
* The methods in this class are able to deal with both UNIX and Windows paths
* with both forward and backward slashes. All methods return normalized parts
* containing only forward slashes and no excess "." and ".." segments.
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Thomas Schulz <mail@king2500.net>
* @author Théo Fidry <theo.fidry@gmail.com>
*/
final class Path
{
/**
* The number of buffer entries that triggers a cleanup operation.
*/
private const CLEANUP_THRESHOLD = 1250;
/**
* The buffer size after the cleanup operation.
*/
private const CLEANUP_SIZE = 1000;
/**
* Buffers input/output of {@link canonicalize()}.
*
* @var array<string, string>
*/
private static $buffer = [];
/**
* @var int
*/
private static $bufferSize = 0;
/**
* Canonicalizes the given path.
*
* During normalization, all slashes are replaced by forward slashes ("/").
* Furthermore, all "." and ".." segments are removed as far as possible.
* ".." segments at the beginning of relative paths are not removed.
*
* ```php
* echo Path::canonicalize("\symfony\puli\..\css\style.css");
* // => /symfony/css/style.css
*
* echo Path::canonicalize("../css/./style.css");
* // => ../css/style.css
* ```
*
* This method is able to deal with both UNIX and Windows paths.
*/
public static function canonicalize(string $path): string
{
if ('' === $path) {
return '';
}
// This method is called by many other methods in this class. Buffer
// the canonicalized paths to make up for the severe performance
// decrease.
if (isset(self::$buffer[$path])) {
return self::$buffer[$path];
}
// Replace "~" with user's home directory.
if ('~' === $path[0]) {
$path = self::getHomeDirectory().substr($path, 1);
}
$path = self::normalize($path);
[$root, $pathWithoutRoot] = self::split($path);
$canonicalParts = self::findCanonicalParts($root, $pathWithoutRoot);
// Add the root directory again
self::$buffer[$path] = $canonicalPath = $root.implode('/', $canonicalParts);
++self::$bufferSize;
// Clean up regularly to prevent memory leaks
if (self::$bufferSize > self::CLEANUP_THRESHOLD) {
self::$buffer = \array_slice(self::$buffer, -self::CLEANUP_SIZE, null, true);
self::$bufferSize = self::CLEANUP_SIZE;
}
return $canonicalPath;
}
/**
* Normalizes the given path.
*
* During normalization, all slashes are replaced by forward slashes ("/").
* Contrary to {@link canonicalize()}, this method does not remove invalid
* or dot path segments. Consequently, it is much more efficient and should
* be used whenever the given path is known to be a valid, absolute system
* path.
*
* This method is able to deal with both UNIX and Windows paths.
*/
public static function normalize(string $path): string
{
return str_replace('\\', '/', $path);
}
/**
* Returns the directory part of the path.
*
* This method is similar to PHP's dirname(), but handles various cases
* where dirname() returns a weird result:
*
* - dirname() does not accept backslashes on UNIX
* - dirname("C:/symfony") returns "C:", not "C:/"
* - dirname("C:/") returns ".", not "C:/"
* - dirname("C:") returns ".", not "C:/"
* - dirname("symfony") returns ".", not ""
* - dirname() does not canonicalize the result
*
* This method fixes these shortcomings and behaves like dirname()
* otherwise.
*
* The result is a canonical path.
*
* @return string The canonical directory part. Returns the root directory
* if the root directory is passed. Returns an empty string
* if a relative path is passed that contains no slashes.
* Returns an empty string if an empty string is passed.
*/
public static function getDirectory(string $path): string
{
if ('' === $path) {
return '';
}
$path = self::canonicalize($path);
// Maintain scheme
if (false !== $schemeSeparatorPosition = strpos($path, '://')) {
$scheme = substr($path, 0, $schemeSeparatorPosition + 3);
$path = substr($path, $schemeSeparatorPosition + 3);
} else {
$scheme = '';
}
if (false === $dirSeparatorPosition = strrpos($path, '/')) {
return '';
}
// Directory equals root directory "/"
if (0 === $dirSeparatorPosition) {
return $scheme.'/';
}
// Directory equals Windows root "C:/"
if (2 === $dirSeparatorPosition && ctype_alpha($path[0]) && ':' === $path[1]) {
return $scheme.substr($path, 0, 3);
}
return $scheme.substr($path, 0, $dirSeparatorPosition);
}
/**
* Returns canonical path of the user's home directory.
*
* Supported operating systems:
*
* - UNIX
* - Windows8 and upper
*
* If your operating system or environment isn't supported, an exception is thrown.
*
* The result is a canonical path.
*
* @throws RuntimeException If your operating system or environment isn't supported
*/
public static function getHomeDirectory(): string
{
// For UNIX support
if (getenv('HOME')) {
return self::canonicalize(getenv('HOME'));
}
// For >= Windows8 support
if (getenv('HOMEDRIVE') && getenv('HOMEPATH')) {
return self::canonicalize(getenv('HOMEDRIVE').getenv('HOMEPATH'));
}
throw new RuntimeException("Cannot find the home directory path: Your environment or operating system isn't supported.");
}
/**
* Returns the root directory of a path.
*
* The result is a canonical path.
*
* @return string The canonical root directory. Returns an empty string if
* the given path is relative or empty.
*/
public static function getRoot(string $path): string
{
if ('' === $path) {
return '';
}
// Maintain scheme
if (false !== $schemeSeparatorPosition = strpos($path, '://')) {
$scheme = substr($path, 0, $schemeSeparatorPosition + 3);
$path = substr($path, $schemeSeparatorPosition + 3);
} else {
$scheme = '';
}
$firstCharacter = $path[0];
// UNIX root "/" or "\" (Windows style)
if ('/' === $firstCharacter || '\\' === $firstCharacter) {
return $scheme.'/';
}
$length = \strlen($path);
// Windows root
if ($length > 1 && ':' === $path[1] && ctype_alpha($firstCharacter)) {
// Special case: "C:"
if (2 === $length) {
return $scheme.$path.'/';
}
// Normal case: "C:/ or "C:\"
if ('/' === $path[2] || '\\' === $path[2]) {
return $scheme.$firstCharacter.$path[1].'/';
}
}
return '';
}
/**
* Returns the file name without the extension from a file path.
*
* @param string|null $extension if specified, only that extension is cut
* off (may contain leading dot)
*/
public static function getFilenameWithoutExtension(string $path, ?string $extension = null): string
{
if ('' === $path) {
return '';
}
if (null !== $extension) {
// remove extension and trailing dot
return rtrim(basename($path, $extension), '.');
}
return pathinfo($path, \PATHINFO_FILENAME);
}
/**
* Returns the extension from a file path (without leading dot).
*
* @param bool $forceLowerCase forces the extension to be lower-case
*/
public static function getExtension(string $path, bool $forceLowerCase = false): string
{
if ('' === $path) {
return '';
}
$extension = pathinfo($path, \PATHINFO_EXTENSION);
if ($forceLowerCase) {
$extension = self::toLower($extension);
}
return $extension;
}
/**
* Returns whether the path has an (or the specified) extension.
*
* @param string $path the path string
* @param string|string[]|null $extensions if null or not provided, checks if
* an extension exists, otherwise
* checks for the specified extension
* or array of extensions (with or
* without leading dot)
* @param bool $ignoreCase whether to ignore case-sensitivity
*/
public static function hasExtension(string $path, $extensions = null, bool $ignoreCase = false): bool
{
if ('' === $path) {
return false;
}
$actualExtension = self::getExtension($path, $ignoreCase);
// Only check if path has any extension
if ([] === $extensions || null === $extensions) {
return '' !== $actualExtension;
}
if (\is_string($extensions)) {
$extensions = [$extensions];
}
foreach ($extensions as $key => $extension) {
if ($ignoreCase) {
$extension = self::toLower($extension);
}
// remove leading '.' in extensions array
$extensions[$key] = ltrim($extension, '.');
}
return \in_array($actualExtension, $extensions, true);
}
/**
* Changes the extension of a path string.
*
* @param string $path The path string with filename.ext to change.
* @param string $extension new extension (with or without leading dot)
*
* @return string the path string with new file extension
*/
public static function changeExtension(string $path, string $extension): string
{
if ('' === $path) {
return '';
}
$actualExtension = self::getExtension($path);
$extension = ltrim($extension, '.');
// No extension for paths
if ('/' === substr($path, -1)) {
return $path;
}
// No actual extension in path
if (empty($actualExtension)) {
return $path.('.' === substr($path, -1) ? '' : '.').$extension;
}
return substr($path, 0, -\strlen($actualExtension)).$extension;
}
public static function isAbsolute(string $path): bool
{
if ('' === $path) {
return false;
}
// Strip scheme
if (false !== ($schemeSeparatorPosition = strpos($path, '://')) && 1 !== $schemeSeparatorPosition) {
$path = substr($path, $schemeSeparatorPosition + 3);
}
$firstCharacter = $path[0];
// UNIX root "/" or "\" (Windows style)
if ('/' === $firstCharacter || '\\' === $firstCharacter) {
return true;
}
// Windows root
if (\strlen($path) > 1 && ctype_alpha($firstCharacter) && ':' === $path[1]) {
// Special case: "C:"
if (2 === \strlen($path)) {
return true;
}
// Normal case: "C:/ or "C:\"
if ('/' === $path[2] || '\\' === $path[2]) {
return true;
}
}
return false;
}
public static function isRelative(string $path): bool
{
return !self::isAbsolute($path);
}
/**
* Turns a relative path into an absolute path in canonical form.
*
* Usually, the relative path is appended to the given base path. Dot
* segments ("." and "..") are removed/collapsed and all slashes turned
* into forward slashes.
*
* ```php
* echo Path::makeAbsolute("../style.css", "/symfony/puli/css");
* // => /symfony/puli/style.css
* ```
*
* If an absolute path is passed, that path is returned unless its root
* directory is different than the one of the base path. In that case, an
* exception is thrown.
*
* ```php
* Path::makeAbsolute("/style.css", "/symfony/puli/css");
* // => /style.css
*
* Path::makeAbsolute("C:/style.css", "C:/symfony/puli/css");
* // => C:/style.css
*
* Path::makeAbsolute("C:/style.css", "/symfony/puli/css");
* // InvalidArgumentException
* ```
*
* If the base path is not an absolute path, an exception is thrown.
*
* The result is a canonical path.
*
* @param string $basePath an absolute base path
*
* @throws InvalidArgumentException if the base path is not absolute or if
* the given path is an absolute path with
* a different root than the base path
*/
public static function makeAbsolute(string $path, string $basePath): string
{
if ('' === $basePath) {
throw new InvalidArgumentException(sprintf('The base path must be a non-empty string. Got: "%s".', $basePath));
}
if (!self::isAbsolute($basePath)) {
throw new InvalidArgumentException(sprintf('The base path "%s" is not an absolute path.', $basePath));
}
if (self::isAbsolute($path)) {
return self::canonicalize($path);
}
if (false !== $schemeSeparatorPosition = strpos($basePath, '://')) {
$scheme = substr($basePath, 0, $schemeSeparatorPosition + 3);
$basePath = substr($basePath, $schemeSeparatorPosition + 3);
} else {
$scheme = '';
}
return $scheme.self::canonicalize(rtrim($basePath, '/\\').'/'.$path);
}
/**
* Turns a path into a relative path.
*
* The relative path is created relative to the given base path:
*
* ```php
* echo Path::makeRelative("/symfony/style.css", "/symfony/puli");
* // => ../style.css
* ```
*
* If a relative path is passed and the base path is absolute, the relative
* path is returned unchanged:
*
* ```php
* Path::makeRelative("style.css", "/symfony/puli/css");
* // => style.css
* ```
*
* If both paths are relative, the relative path is created with the
* assumption that both paths are relative to the same directory:
*
* ```php
* Path::makeRelative("style.css", "symfony/puli/css");
* // => ../../../style.css
* ```
*
* If both paths are absolute, their root directory must be the same,
* otherwise an exception is thrown:
*
* ```php
* Path::makeRelative("C:/symfony/style.css", "/symfony/puli");
* // InvalidArgumentException
* ```
*
* If the passed path is absolute, but the base path is not, an exception
* is thrown as well:
*
* ```php
* Path::makeRelative("/symfony/style.css", "symfony/puli");
* // InvalidArgumentException
* ```
*
* If the base path is not an absolute path, an exception is thrown.
*
* The result is a canonical path.
*
* @throws InvalidArgumentException if the base path is not absolute or if
* the given path has a different root
* than the base path
*/
public static function makeRelative(string $path, string $basePath): string
{
$path = self::canonicalize($path);
$basePath = self::canonicalize($basePath);
[$root, $relativePath] = self::split($path);
[$baseRoot, $relativeBasePath] = self::split($basePath);
// If the base path is given as absolute path and the path is already
// relative, consider it to be relative to the given absolute path
// already
if ('' === $root && '' !== $baseRoot) {
// If base path is already in its root
if ('' === $relativeBasePath) {
$relativePath = ltrim($relativePath, './\\');
}
return $relativePath;
}
// If the passed path is absolute, but the base path is not, we
// cannot generate a relative path
if ('' !== $root && '' === $baseRoot) {
throw new InvalidArgumentException(sprintf('The absolute path "%s" cannot be made relative to the relative path "%s". You should provide an absolute base path instead.', $path, $basePath));
}
// Fail if the roots of the two paths are different
if ($baseRoot && $root !== $baseRoot) {
throw new InvalidArgumentException(sprintf('The path "%s" cannot be made relative to "%s", because they have different roots ("%s" and "%s").', $path, $basePath, $root, $baseRoot));
}
if ('' === $relativeBasePath) {
return $relativePath;
}
// Build a "../../" prefix with as many "../" parts as necessary
$parts = explode('/', $relativePath);
$baseParts = explode('/', $relativeBasePath);
$dotDotPrefix = '';
// Once we found a non-matching part in the prefix, we need to add
// "../" parts for all remaining parts
$match = true;
foreach ($baseParts as $index => $basePart) {
if ($match && isset($parts[$index]) && $basePart === $parts[$index]) {
unset($parts[$index]);
continue;
}
$match = false;
$dotDotPrefix .= '../';
}
return rtrim($dotDotPrefix.implode('/', $parts), '/');
}
/**
* Returns whether the given path is on the local filesystem.
*/
public static function isLocal(string $path): bool
{
return '' !== $path && false === strpos($path, '://');
}
/**
* Returns the longest common base path in canonical form of a set of paths or
* `null` if the paths are on different Windows partitions.
*
* Dot segments ("." and "..") are removed/collapsed and all slashes turned
* into forward slashes.
*
* ```php
* $basePath = Path::getLongestCommonBasePath(
* '/symfony/css/style.css',
* '/symfony/css/..'
* );
* // => /symfony
* ```
*
* The root is returned if no common base path can be found:
*
* ```php
* $basePath = Path::getLongestCommonBasePath(
* '/symfony/css/style.css',
* '/puli/css/..'
* );
* // => /
* ```
*
* If the paths are located on different Windows partitions, `null` is
* returned.
*
* ```php
* $basePath = Path::getLongestCommonBasePath(
* 'C:/symfony/css/style.css',
* 'D:/symfony/css/..'
* );
* // => null
* ```
*/
public static function getLongestCommonBasePath(string ...$paths): ?string
{
[$bpRoot, $basePath] = self::split(self::canonicalize(reset($paths)));
for (next($paths); null !== key($paths) && '' !== $basePath; next($paths)) {
[$root, $path] = self::split(self::canonicalize(current($paths)));
// If we deal with different roots (e.g. C:/ vs. D:/), it's time
// to quit
if ($root !== $bpRoot) {
return null;
}
// Make the base path shorter until it fits into path
while (true) {
if ('.' === $basePath) {
// No more base paths
$basePath = '';
// next path
continue 2;
}
// Prevent false positives for common prefixes
// see isBasePath()
if (0 === strpos($path.'/', $basePath.'/')) {
// next path
continue 2;
}
$basePath = \dirname($basePath);
}
}
return $bpRoot.$basePath;
}
/**
* Joins two or more path strings into a canonical path.
*/
public static function join(string ...$paths): string
{
$finalPath = null;
$wasScheme = false;
foreach ($paths as $path) {
if ('' === $path) {
continue;
}
if (null === $finalPath) {
// For first part we keep slashes, like '/top', 'C:\' or 'phar://'
$finalPath = $path;
$wasScheme = (false !== strpos($path, '://'));
continue;
}
// Only add slash if previous part didn't end with '/' or '\'
if (!\in_array(substr($finalPath, -1), ['/', '\\'])) {
$finalPath .= '/';
}
// If first part included a scheme like 'phar://' we allow \current part to start with '/', otherwise trim
$finalPath .= $wasScheme ? $path : ltrim($path, '/');
$wasScheme = false;
}
if (null === $finalPath) {
return '';
}
return self::canonicalize($finalPath);
}
/**
* Returns whether a path is a base path of another path.
*
* Dot segments ("." and "..") are removed/collapsed and all slashes turned
* into forward slashes.
*
* ```php
* Path::isBasePath('/symfony', '/symfony/css');
* // => true
*
* Path::isBasePath('/symfony', '/symfony');
* // => true
*
* Path::isBasePath('/symfony', '/symfony/..');
* // => false
*
* Path::isBasePath('/symfony', '/puli');
* // => false
* ```
*/
public static function isBasePath(string $basePath, string $ofPath): bool
{
$basePath = self::canonicalize($basePath);
$ofPath = self::canonicalize($ofPath);
// Append slashes to prevent false positives when two paths have
// a common prefix, for example /base/foo and /base/foobar.
// Don't append a slash for the root "/", because then that root
// won't be discovered as common prefix ("//" is not a prefix of
// "/foobar/").
return 0 === strpos($ofPath.'/', rtrim($basePath, '/').'/');
}
/**
* @return string[]
*/
private static function findCanonicalParts(string $root, string $pathWithoutRoot): array
{
$parts = explode('/', $pathWithoutRoot);
$canonicalParts = [];
// Collapse "." and "..", if possible
foreach ($parts as $part) {
if ('.' === $part || '' === $part) {
continue;
}
// Collapse ".." with the previous part, if one exists
// Don't collapse ".." if the previous part is also ".."
if ('..' === $part && \count($canonicalParts) > 0 && '..' !== $canonicalParts[\count($canonicalParts) - 1]) {
array_pop($canonicalParts);
continue;
}
// Only add ".." prefixes for relative paths
if ('..' !== $part || '' === $root) {
$canonicalParts[] = $part;
}
}
return $canonicalParts;
}
/**
* Splits a canonical path into its root directory and the remainder.
*
* If the path has no root directory, an empty root directory will be
* returned.
*
* If the root directory is a Windows style partition, the resulting root
* will always contain a trailing slash.
*
* list ($root, $path) = Path::split("C:/symfony")
* // => ["C:/", "symfony"]
*
* list ($root, $path) = Path::split("C:")
* // => ["C:/", ""]
*
* @return array{string, string} an array with the root directory and the remaining relative path
*/
private static function split(string $path): array
{
if ('' === $path) {
return ['', ''];
}
// Remember scheme as part of the root, if any
if (false !== $schemeSeparatorPosition = strpos($path, '://')) {
$root = substr($path, 0, $schemeSeparatorPosition + 3);
$path = substr($path, $schemeSeparatorPosition + 3);
} else {
$root = '';
}
$length = \strlen($path);
// Remove and remember root directory
if (0 === strpos($path, '/')) {
$root .= '/';
$path = $length > 1 ? substr($path, 1) : '';
} elseif ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) {
if (2 === $length) {
// Windows special case: "C:"
$root .= $path.'/';
$path = '';
} elseif ('/' === $path[2]) {
// Windows normal case: "C:/"..
$root .= substr($path, 0, 3);
$path = $length > 3 ? substr($path, 3) : '';
}
}
return [$root, $path];
}
private static function toLower(string $string): string
{
if (false !== $encoding = mb_detect_encoding($string, null, true)) {
return mb_strtolower($string, $encoding);
}
return strtolower($string);
}
private function __construct()
{
}
}

13
vendor/symfony/filesystem/README.md vendored Normal file
View file

@ -0,0 +1,13 @@
Filesystem Component
====================
The Filesystem component provides basic utilities for the filesystem.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/filesystem.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

34
vendor/symfony/filesystem/composer.json vendored Normal file
View file

@ -0,0 +1,34 @@
{
"name": "symfony/filesystem",
"type": "library",
"description": "Provides basic utilities for the filesystem",
"keywords": [],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.8",
"symfony/polyfill-php80": "^1.16"
},
"require-dev": {
"symfony/process": "^5.4|^6.4"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Filesystem\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev"
}

87
vendor/symfony/finder/CHANGELOG.md vendored Normal file
View file

@ -0,0 +1,87 @@
CHANGELOG
=========
5.4.0
-----
* Deprecate `Comparator::setTarget()` and `Comparator::setOperator()`
* Add a constructor to `Comparator` that allows setting target and operator
* Finder's iterator has now `Symfony\Component\Finder\SplFileInfo` inner type specified
* Add recursive .gitignore files support
5.0.0
-----
* added `$useNaturalSort` argument to `Finder::sortByName()`
4.3.0
-----
* added Finder::ignoreVCSIgnored() to ignore files based on rules listed in .gitignore
4.2.0
-----
* added $useNaturalSort option to Finder::sortByName() method
* the `Finder::sortByName()` method will have a new `$useNaturalSort`
argument in version 5.0, not defining it is deprecated
* added `Finder::reverseSorting()` to reverse the sorting
4.0.0
-----
* removed `ExceptionInterface`
* removed `Symfony\Component\Finder\Iterator\FilterIterator`
3.4.0
-----
* deprecated `Symfony\Component\Finder\Iterator\FilterIterator`
* added Finder::hasResults() method to check if any results were found
3.3.0
-----
* added double-star matching to Glob::toRegex()
3.0.0
-----
* removed deprecated classes
2.8.0
-----
* deprecated adapters and related classes
2.5.0
-----
* added support for GLOB_BRACE in the paths passed to Finder::in()
2.3.0
-----
* added a way to ignore unreadable directories (via Finder::ignoreUnreadableDirs())
* unified the way subfolders that are not executable are handled by always throwing an AccessDeniedException exception
2.2.0
-----
* added Finder::path() and Finder::notPath() methods
* added finder adapters to improve performance on specific platforms
* added support for wildcard characters (glob patterns) in the paths passed
to Finder::in()
2.1.0
-----
* added Finder::sortByAccessedTime(), Finder::sortByChangedTime(), and
Finder::sortByModifiedTime()
* added Countable to Finder
* added support for an array of directories as an argument to
Finder::exclude()
* added searching based on the file content via Finder::contains() and
Finder::notContains()
* added support for the != operator in the Comparator
* [BC BREAK] filter expressions (used for file name and content) are no more
considered as regexps but glob patterns when they are enclosed in '*' or '?'

View file

@ -0,0 +1,117 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class Comparator
{
private $target;
private $operator = '==';
public function __construct(?string $target = null, string $operator = '==')
{
if (null === $target) {
trigger_deprecation('symfony/finder', '5.4', 'Constructing a "%s" without setting "$target" is deprecated.', __CLASS__);
}
$this->target = $target;
$this->doSetOperator($operator);
}
/**
* Gets the target value.
*
* @return string
*/
public function getTarget()
{
if (null === $this->target) {
trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__);
}
return $this->target;
}
/**
* @deprecated set the target via the constructor instead
*/
public function setTarget(string $target)
{
trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the target via the constructor instead.', __METHOD__);
$this->target = $target;
}
/**
* Gets the comparison operator.
*
* @return string
*/
public function getOperator()
{
return $this->operator;
}
/**
* Sets the comparison operator.
*
* @throws \InvalidArgumentException
*
* @deprecated set the operator via the constructor instead
*/
public function setOperator(string $operator)
{
trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the operator via the constructor instead.', __METHOD__);
$this->doSetOperator('' === $operator ? '==' : $operator);
}
/**
* Tests against the target.
*
* @param mixed $test A test value
*
* @return bool
*/
public function test($test)
{
if (null === $this->target) {
trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__);
}
switch ($this->operator) {
case '>':
return $test > $this->target;
case '>=':
return $test >= $this->target;
case '<':
return $test < $this->target;
case '<=':
return $test <= $this->target;
case '!=':
return $test != $this->target;
}
return $test == $this->target;
}
private function doSetOperator(string $operator): void
{
if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) {
throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator));
}
$this->operator = $operator;
}
}

View file

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* DateCompare compiles date comparisons.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DateComparator extends Comparator
{
/**
* @param string $test A comparison string
*
* @throws \InvalidArgumentException If the test is not understood
*/
public function __construct(string $test)
{
if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) {
throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test));
}
try {
$date = new \DateTime($matches[2]);
$target = $date->format('U');
} catch (\Exception $e) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2]));
}
$operator = $matches[1] ?? '==';
if ('since' === $operator || 'after' === $operator) {
$operator = '>';
}
if ('until' === $operator || 'before' === $operator) {
$operator = '<';
}
parent::__construct($target, $operator);
}
}

View file

@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* NumberComparator compiles a simple comparison to an anonymous
* subroutine, which you can call with a value to be tested again.
*
* Now this would be very pointless, if NumberCompare didn't understand
* magnitudes.
*
* The target value may use magnitudes of kilobytes (k, ki),
* megabytes (m, mi), or gigabytes (g, gi). Those suffixed
* with an i use the appropriate 2**n version in accordance with the
* IEC standard: http://physics.nist.gov/cuu/Units/binary.html
*
* Based on the Perl Number::Compare module.
*
* @author Fabien Potencier <fabien@symfony.com> PHP port
* @author Richard Clamp <richardc@unixbeard.net> Perl version
* @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
* @copyright 2002 Richard Clamp <richardc@unixbeard.net>
*
* @see http://physics.nist.gov/cuu/Units/binary.html
*/
class NumberComparator extends Comparator
{
/**
* @param string|null $test A comparison string or null
*
* @throws \InvalidArgumentException If the test is not understood
*/
public function __construct(?string $test)
{
if (null === $test || !preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) {
throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test ?? 'null'));
}
$target = $matches[2];
if (!is_numeric($target)) {
throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target));
}
if (isset($matches[3])) {
// magnitude
switch (strtolower($matches[3])) {
case 'k':
$target *= 1000;
break;
case 'ki':
$target *= 1024;
break;
case 'm':
$target *= 1000000;
break;
case 'mi':
$target *= 1024 * 1024;
break;
case 'g':
$target *= 1000000000;
break;
case 'gi':
$target *= 1024 * 1024 * 1024;
break;
}
}
parent::__construct($target, $matches[1] ?: '==');
}
}

View file

@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Exception;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*/
class AccessDeniedException extends \UnexpectedValueException
{
}

View file

@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Exception;
/**
* @author Andreas Erhard <andreas.erhard@i-med.ac.at>
*/
class DirectoryNotFoundException extends \InvalidArgumentException
{
}

806
vendor/symfony/finder/Finder.php vendored Normal file
View file

@ -0,0 +1,806 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
use Symfony\Component\Finder\Comparator\DateComparator;
use Symfony\Component\Finder\Comparator\NumberComparator;
use Symfony\Component\Finder\Exception\DirectoryNotFoundException;
use Symfony\Component\Finder\Iterator\CustomFilterIterator;
use Symfony\Component\Finder\Iterator\DateRangeFilterIterator;
use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator;
use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator;
use Symfony\Component\Finder\Iterator\FilecontentFilterIterator;
use Symfony\Component\Finder\Iterator\FilenameFilterIterator;
use Symfony\Component\Finder\Iterator\LazyIterator;
use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator;
use Symfony\Component\Finder\Iterator\SortableIterator;
/**
* Finder allows to build rules to find files and directories.
*
* It is a thin wrapper around several specialized iterator classes.
*
* All rules may be invoked several times.
*
* All methods return the current Finder object to allow chaining:
*
* $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @implements \IteratorAggregate<string, SplFileInfo>
*/
class Finder implements \IteratorAggregate, \Countable
{
public const IGNORE_VCS_FILES = 1;
public const IGNORE_DOT_FILES = 2;
public const IGNORE_VCS_IGNORED_FILES = 4;
private $mode = 0;
private $names = [];
private $notNames = [];
private $exclude = [];
private $filters = [];
private $depths = [];
private $sizes = [];
private $followLinks = false;
private $reverseSorting = false;
private $sort = false;
private $ignore = 0;
private $dirs = [];
private $dates = [];
private $iterators = [];
private $contains = [];
private $notContains = [];
private $paths = [];
private $notPaths = [];
private $ignoreUnreadableDirs = false;
private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'];
public function __construct()
{
$this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
}
/**
* Creates a new Finder.
*
* @return static
*/
public static function create()
{
return new static();
}
/**
* Restricts the matching to directories only.
*
* @return $this
*/
public function directories()
{
$this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;
return $this;
}
/**
* Restricts the matching to files only.
*
* @return $this
*/
public function files()
{
$this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;
return $this;
}
/**
* Adds tests for the directory depth.
*
* Usage:
*
* $finder->depth('> 1') // the Finder will start matching at level 1.
* $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point.
* $finder->depth(['>= 1', '< 3'])
*
* @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels
*
* @return $this
*
* @see DepthRangeFilterIterator
* @see NumberComparator
*/
public function depth($levels)
{
foreach ((array) $levels as $level) {
$this->depths[] = new Comparator\NumberComparator($level);
}
return $this;
}
/**
* Adds tests for file dates (last modified).
*
* The date must be something that strtotime() is able to parse:
*
* $finder->date('since yesterday');
* $finder->date('until 2 days ago');
* $finder->date('> now - 2 hours');
* $finder->date('>= 2005-10-15');
* $finder->date(['>= 2005-10-15', '<= 2006-05-27']);
*
* @param string|string[] $dates A date range string or an array of date ranges
*
* @return $this
*
* @see strtotime
* @see DateRangeFilterIterator
* @see DateComparator
*/
public function date($dates)
{
foreach ((array) $dates as $date) {
$this->dates[] = new Comparator\DateComparator($date);
}
return $this;
}
/**
* Adds rules that files must match.
*
* You can use patterns (delimited with / sign), globs or simple strings.
*
* $finder->name('/\.php$/')
* $finder->name('*.php') // same as above, without dot files
* $finder->name('test.php')
* $finder->name(['test.py', 'test.php'])
*
* @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function name($patterns)
{
$this->names = array_merge($this->names, (array) $patterns);
return $this;
}
/**
* Adds rules that files must not match.
*
* @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function notName($patterns)
{
$this->notNames = array_merge($this->notNames, (array) $patterns);
return $this;
}
/**
* Adds tests that file contents must match.
*
* Strings or PCRE patterns can be used:
*
* $finder->contains('Lorem ipsum')
* $finder->contains('/Lorem ipsum/i')
* $finder->contains(['dolor', '/ipsum/i'])
*
* @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
*
* @return $this
*
* @see FilecontentFilterIterator
*/
public function contains($patterns)
{
$this->contains = array_merge($this->contains, (array) $patterns);
return $this;
}
/**
* Adds tests that file contents must not match.
*
* Strings or PCRE patterns can be used:
*
* $finder->notContains('Lorem ipsum')
* $finder->notContains('/Lorem ipsum/i')
* $finder->notContains(['lorem', '/dolor/i'])
*
* @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
*
* @return $this
*
* @see FilecontentFilterIterator
*/
public function notContains($patterns)
{
$this->notContains = array_merge($this->notContains, (array) $patterns);
return $this;
}
/**
* Adds rules that filenames must match.
*
* You can use patterns (delimited with / sign) or simple strings.
*
* $finder->path('some/special/dir')
* $finder->path('/some\/special\/dir/') // same as above
* $finder->path(['some dir', 'another/dir'])
*
* Use only / as dirname separator.
*
* @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function path($patterns)
{
$this->paths = array_merge($this->paths, (array) $patterns);
return $this;
}
/**
* Adds rules that filenames must not match.
*
* You can use patterns (delimited with / sign) or simple strings.
*
* $finder->notPath('some/special/dir')
* $finder->notPath('/some\/special\/dir/') // same as above
* $finder->notPath(['some/file.txt', 'another/file.log'])
*
* Use only / as dirname separator.
*
* @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function notPath($patterns)
{
$this->notPaths = array_merge($this->notPaths, (array) $patterns);
return $this;
}
/**
* Adds tests for file sizes.
*
* $finder->size('> 10K');
* $finder->size('<= 1Ki');
* $finder->size(4);
* $finder->size(['> 10K', '< 20K'])
*
* @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges
*
* @return $this
*
* @see SizeRangeFilterIterator
* @see NumberComparator
*/
public function size($sizes)
{
foreach ((array) $sizes as $size) {
$this->sizes[] = new Comparator\NumberComparator($size);
}
return $this;
}
/**
* Excludes directories.
*
* Directories passed as argument must be relative to the ones defined with the `in()` method. For example:
*
* $finder->in(__DIR__)->exclude('ruby');
*
* @param string|array $dirs A directory path or an array of directories
*
* @return $this
*
* @see ExcludeDirectoryFilterIterator
*/
public function exclude($dirs)
{
$this->exclude = array_merge($this->exclude, (array) $dirs);
return $this;
}
/**
* Excludes "hidden" directories and files (starting with a dot).
*
* This option is enabled by default.
*
* @return $this
*
* @see ExcludeDirectoryFilterIterator
*/
public function ignoreDotFiles(bool $ignoreDotFiles)
{
if ($ignoreDotFiles) {
$this->ignore |= static::IGNORE_DOT_FILES;
} else {
$this->ignore &= ~static::IGNORE_DOT_FILES;
}
return $this;
}
/**
* Forces the finder to ignore version control directories.
*
* This option is enabled by default.
*
* @return $this
*
* @see ExcludeDirectoryFilterIterator
*/
public function ignoreVCS(bool $ignoreVCS)
{
if ($ignoreVCS) {
$this->ignore |= static::IGNORE_VCS_FILES;
} else {
$this->ignore &= ~static::IGNORE_VCS_FILES;
}
return $this;
}
/**
* Forces Finder to obey .gitignore and ignore files based on rules listed there.
*
* This option is disabled by default.
*
* @return $this
*/
public function ignoreVCSIgnored(bool $ignoreVCSIgnored)
{
if ($ignoreVCSIgnored) {
$this->ignore |= static::IGNORE_VCS_IGNORED_FILES;
} else {
$this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES;
}
return $this;
}
/**
* Adds VCS patterns.
*
* @see ignoreVCS()
*
* @param string|string[] $pattern VCS patterns to ignore
*/
public static function addVCSPattern($pattern)
{
foreach ((array) $pattern as $p) {
self::$vcsPatterns[] = $p;
}
self::$vcsPatterns = array_unique(self::$vcsPatterns);
}
/**
* Sorts files and directories by an anonymous function.
*
* The anonymous function receives two \SplFileInfo instances to compare.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sort(\Closure $closure)
{
$this->sort = $closure;
return $this;
}
/**
* Sorts files and directories by name.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByName(bool $useNaturalSort = false)
{
$this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME;
return $this;
}
/**
* Sorts files and directories by type (directories before files), then by name.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByType()
{
$this->sort = Iterator\SortableIterator::SORT_BY_TYPE;
return $this;
}
/**
* Sorts files and directories by the last accessed time.
*
* This is the time that the file was last accessed, read or written to.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByAccessedTime()
{
$this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME;
return $this;
}
/**
* Reverses the sorting.
*
* @return $this
*/
public function reverseSorting()
{
$this->reverseSorting = true;
return $this;
}
/**
* Sorts files and directories by the last inode changed time.
*
* This is the time that the inode information was last modified (permissions, owner, group or other metadata).
*
* On Windows, since inode is not available, changed time is actually the file creation time.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByChangedTime()
{
$this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME;
return $this;
}
/**
* Sorts files and directories by the last modified time.
*
* This is the last time the actual contents of the file were last modified.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByModifiedTime()
{
$this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME;
return $this;
}
/**
* Filters the iterator with an anonymous function.
*
* The anonymous function receives a \SplFileInfo and must return false
* to remove files.
*
* @return $this
*
* @see CustomFilterIterator
*/
public function filter(\Closure $closure)
{
$this->filters[] = $closure;
return $this;
}
/**
* Forces the following of symlinks.
*
* @return $this
*/
public function followLinks()
{
$this->followLinks = true;
return $this;
}
/**
* Tells finder to ignore unreadable directories.
*
* By default, scanning unreadable directories content throws an AccessDeniedException.
*
* @return $this
*/
public function ignoreUnreadableDirs(bool $ignore = true)
{
$this->ignoreUnreadableDirs = $ignore;
return $this;
}
/**
* Searches files and directories which match defined rules.
*
* @param string|string[] $dirs A directory path or an array of directories
*
* @return $this
*
* @throws DirectoryNotFoundException if one of the directories does not exist
*/
public function in($dirs)
{
$resolvedDirs = [];
foreach ((array) $dirs as $dir) {
if (is_dir($dir)) {
$resolvedDirs[] = [$this->normalizeDir($dir)];
} elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) {
sort($glob);
$resolvedDirs[] = array_map([$this, 'normalizeDir'], $glob);
} else {
throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir));
}
}
$this->dirs = array_merge($this->dirs, ...$resolvedDirs);
return $this;
}
/**
* Returns an Iterator for the current Finder configuration.
*
* This method implements the IteratorAggregate interface.
*
* @return \Iterator<string, SplFileInfo>
*
* @throws \LogicException if the in() method has not been called
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
if (0 === \count($this->dirs) && 0 === \count($this->iterators)) {
throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
}
if (1 === \count($this->dirs) && 0 === \count($this->iterators)) {
$iterator = $this->searchInDirectory($this->dirs[0]);
if ($this->sort || $this->reverseSorting) {
$iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator();
}
return $iterator;
}
$iterator = new \AppendIterator();
foreach ($this->dirs as $dir) {
$iterator->append(new \IteratorIterator(new LazyIterator(function () use ($dir) {
return $this->searchInDirectory($dir);
})));
}
foreach ($this->iterators as $it) {
$iterator->append($it);
}
if ($this->sort || $this->reverseSorting) {
$iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator();
}
return $iterator;
}
/**
* Appends an existing set of files/directories to the finder.
*
* The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
*
* @return $this
*
* @throws \InvalidArgumentException when the given argument is not iterable
*/
public function append(iterable $iterator)
{
if ($iterator instanceof \IteratorAggregate) {
$this->iterators[] = $iterator->getIterator();
} elseif ($iterator instanceof \Iterator) {
$this->iterators[] = $iterator;
} elseif (is_iterable($iterator)) {
$it = new \ArrayIterator();
foreach ($iterator as $file) {
$file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file);
$it[$file->getPathname()] = $file;
}
$this->iterators[] = $it;
} else {
throw new \InvalidArgumentException('Finder::append() method wrong argument type.');
}
return $this;
}
/**
* Check if any results were found.
*
* @return bool
*/
public function hasResults()
{
foreach ($this->getIterator() as $_) {
return true;
}
return false;
}
/**
* Counts all the results collected by the iterators.
*
* @return int
*/
#[\ReturnTypeWillChange]
public function count()
{
return iterator_count($this->getIterator());
}
private function searchInDirectory(string $dir): \Iterator
{
$exclude = $this->exclude;
$notPaths = $this->notPaths;
if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
$exclude = array_merge($exclude, self::$vcsPatterns);
}
if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
$notPaths[] = '#(^|/)\..+(/|$)#';
}
$minDepth = 0;
$maxDepth = \PHP_INT_MAX;
foreach ($this->depths as $comparator) {
switch ($comparator->getOperator()) {
case '>':
$minDepth = $comparator->getTarget() + 1;
break;
case '>=':
$minDepth = $comparator->getTarget();
break;
case '<':
$maxDepth = $comparator->getTarget() - 1;
break;
case '<=':
$maxDepth = $comparator->getTarget();
break;
default:
$minDepth = $maxDepth = $comparator->getTarget();
}
}
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
if ($this->followLinks) {
$flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
}
$iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs);
if ($exclude) {
$iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude);
}
$iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);
if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) {
$iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth);
}
if ($this->mode) {
$iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
}
if ($this->names || $this->notNames) {
$iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames);
}
if ($this->contains || $this->notContains) {
$iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
}
if ($this->sizes) {
$iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes);
}
if ($this->dates) {
$iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates);
}
if ($this->filters) {
$iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
}
if ($this->paths || $notPaths) {
$iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths);
}
if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
$iterator = new Iterator\VcsIgnoredFilterIterator($iterator, $dir);
}
return $iterator;
}
/**
* Normalizes given directory names by removing trailing slashes.
*
* Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper
*/
private function normalizeDir(string $dir): string
{
if ('/' === $dir) {
return $dir;
}
$dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR);
if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) {
$dir .= '/';
}
return $dir;
}
}

93
vendor/symfony/finder/Gitignore.php vendored Normal file
View file

@ -0,0 +1,93 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Gitignore matches against text.
*
* @author Michael Voříšek <vorismi3@fel.cvut.cz>
* @author Ahmed Abdou <mail@ahmd.io>
*/
class Gitignore
{
/**
* Returns a regexp which is the equivalent of the gitignore pattern.
*
* Format specification: https://git-scm.com/docs/gitignore#_pattern_format
*/
public static function toRegex(string $gitignoreFileContent): string
{
return self::buildRegex($gitignoreFileContent, false);
}
public static function toRegexMatchingNegatedPatterns(string $gitignoreFileContent): string
{
return self::buildRegex($gitignoreFileContent, true);
}
private static function buildRegex(string $gitignoreFileContent, bool $inverted): string
{
$gitignoreFileContent = preg_replace('~(?<!\\\\)#[^\n\r]*~', '', $gitignoreFileContent);
$gitignoreLines = preg_split('~\r\n?|\n~', $gitignoreFileContent);
$res = self::lineToRegex('');
foreach ($gitignoreLines as $line) {
$line = preg_replace('~(?<!\\\\)[ \t]+$~', '', $line);
if ('!' === substr($line, 0, 1)) {
$line = substr($line, 1);
$isNegative = true;
} else {
$isNegative = false;
}
if ('' !== $line) {
if ($isNegative xor $inverted) {
$res = '(?!'.self::lineToRegex($line).'$)'.$res;
} else {
$res = '(?:'.$res.'|'.self::lineToRegex($line).')';
}
}
}
return '~^(?:'.$res.')~s';
}
private static function lineToRegex(string $gitignoreLine): string
{
if ('' === $gitignoreLine) {
return '$f'; // always false
}
$slashPos = strpos($gitignoreLine, '/');
if (false !== $slashPos && \strlen($gitignoreLine) - 1 !== $slashPos) {
if (0 === $slashPos) {
$gitignoreLine = substr($gitignoreLine, 1);
}
$isAbsolute = true;
} else {
$isAbsolute = false;
}
$regex = preg_quote(str_replace('\\', '', $gitignoreLine), '~');
$regex = preg_replace_callback('~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', function (array $matches): string {
return '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']';
}, $regex);
$regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(?<!//))+$1)?', $regex);
$regex = preg_replace('~\\\\\*~', '[^/]*', $regex);
$regex = preg_replace('~\\\\\?~', '[^/]', $regex);
return ($isAbsolute ? '' : '(?:[^/]+/)*')
.$regex
.(!str_ends_with($gitignoreLine, '/') ? '(?:$|/)' : '');
}
}

111
vendor/symfony/finder/Glob.php vendored Normal file
View file

@ -0,0 +1,111 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Glob matches globbing patterns against text.
*
* if match_glob("foo.*", "foo.bar") echo "matched\n";
*
* // prints foo.bar and foo.baz
* $regex = glob_to_regex("foo.*");
* for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t)
* {
* if (/$regex/) echo "matched: $car\n";
* }
*
* Glob implements glob(3) style matching that can be used to match
* against text, rather than fetching names from a filesystem.
*
* Based on the Perl Text::Glob module.
*
* @author Fabien Potencier <fabien@symfony.com> PHP port
* @author Richard Clamp <richardc@unixbeard.net> Perl version
* @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
* @copyright 2002 Richard Clamp <richardc@unixbeard.net>
*/
class Glob
{
/**
* Returns a regexp which is the equivalent of the glob pattern.
*
* @return string
*/
public static function toRegex(string $glob, bool $strictLeadingDot = true, bool $strictWildcardSlash = true, string $delimiter = '#')
{
$firstByte = true;
$escaping = false;
$inCurlies = 0;
$regex = '';
$sizeGlob = \strlen($glob);
for ($i = 0; $i < $sizeGlob; ++$i) {
$car = $glob[$i];
if ($firstByte && $strictLeadingDot && '.' !== $car) {
$regex .= '(?=[^\.])';
}
$firstByte = '/' === $car;
if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) {
$car = '[^/]++/';
if (!isset($glob[$i + 3])) {
$car .= '?';
}
if ($strictLeadingDot) {
$car = '(?=[^\.])'.$car;
}
$car = '/(?:'.$car.')*';
$i += 2 + isset($glob[$i + 3]);
if ('/' === $delimiter) {
$car = str_replace('/', '\\/', $car);
}
}
if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) {
$regex .= "\\$car";
} elseif ('*' === $car) {
$regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*');
} elseif ('?' === $car) {
$regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.');
} elseif ('{' === $car) {
$regex .= $escaping ? '\\{' : '(';
if (!$escaping) {
++$inCurlies;
}
} elseif ('}' === $car && $inCurlies) {
$regex .= $escaping ? '}' : ')';
if (!$escaping) {
--$inCurlies;
}
} elseif (',' === $car && $inCurlies) {
$regex .= $escaping ? ',' : '|';
} elseif ('\\' === $car) {
if ($escaping) {
$regex .= '\\\\';
$escaping = false;
} else {
$escaping = true;
}
continue;
} else {
$regex .= $car;
}
$escaping = false;
}
return $delimiter.'^'.$regex.'$'.$delimiter;
}
}

View file

@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* CustomFilterIterator filters files by applying anonymous functions.
*
* The anonymous function receives a \SplFileInfo and must return false
* to remove files.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @extends \FilterIterator<string, \SplFileInfo>
*/
class CustomFilterIterator extends \FilterIterator
{
private $filters = [];
/**
* @param \Iterator<string, \SplFileInfo> $iterator The Iterator to filter
* @param callable[] $filters An array of PHP callbacks
*
* @throws \InvalidArgumentException
*/
public function __construct(\Iterator $iterator, array $filters)
{
foreach ($filters as $filter) {
if (!\is_callable($filter)) {
throw new \InvalidArgumentException('Invalid PHP callback.');
}
}
$this->filters = $filters;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
$fileinfo = $this->current();
foreach ($this->filters as $filter) {
if (false === $filter($fileinfo)) {
return false;
}
}
return true;
}
}

View file

@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Comparator\DateComparator;
/**
* DateRangeFilterIterator filters out files that are not in the given date range (last modified dates).
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @extends \FilterIterator<string, \SplFileInfo>
*/
class DateRangeFilterIterator extends \FilterIterator
{
private $comparators = [];
/**
* @param \Iterator<string, \SplFileInfo> $iterator
* @param DateComparator[] $comparators
*/
public function __construct(\Iterator $iterator, array $comparators)
{
$this->comparators = $comparators;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
$fileinfo = $this->current();
if (!file_exists($fileinfo->getPathname())) {
return false;
}
$filedate = $fileinfo->getMTime();
foreach ($this->comparators as $compare) {
if (!$compare->test($filedate)) {
return false;
}
}
return true;
}
}

View file

@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* DepthRangeFilterIterator limits the directory depth.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @template-covariant TKey
* @template-covariant TValue
*
* @extends \FilterIterator<TKey, TValue>
*/
class DepthRangeFilterIterator extends \FilterIterator
{
private $minDepth = 0;
/**
* @param \RecursiveIteratorIterator<\RecursiveIterator<TKey, TValue>> $iterator The Iterator to filter
* @param int $minDepth The min depth
* @param int $maxDepth The max depth
*/
public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = \PHP_INT_MAX)
{
$this->minDepth = $minDepth;
$iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth);
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
return $this->getInnerIterator()->getDepth() >= $this->minDepth;
}
}

View file

@ -0,0 +1,97 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* ExcludeDirectoryFilterIterator filters out directories.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @extends \FilterIterator<string, \SplFileInfo>
*
* @implements \RecursiveIterator<string, \SplFileInfo>
*/
class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator
{
private $iterator;
private $isRecursive;
private $excludedDirs = [];
private $excludedPattern;
/**
* @param \Iterator $iterator The Iterator to filter
* @param string[] $directories An array of directories to exclude
*/
public function __construct(\Iterator $iterator, array $directories)
{
$this->iterator = $iterator;
$this->isRecursive = $iterator instanceof \RecursiveIterator;
$patterns = [];
foreach ($directories as $directory) {
$directory = rtrim($directory, '/');
if (!$this->isRecursive || str_contains($directory, '/')) {
$patterns[] = preg_quote($directory, '#');
} else {
$this->excludedDirs[$directory] = true;
}
}
if ($patterns) {
$this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#';
}
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) {
return false;
}
if ($this->excludedPattern) {
$path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath();
$path = str_replace('\\', '/', $path);
return !preg_match($this->excludedPattern, $path);
}
return true;
}
/**
* @return bool
*/
#[\ReturnTypeWillChange]
public function hasChildren()
{
return $this->isRecursive && $this->iterator->hasChildren();
}
/**
* @return self
*/
#[\ReturnTypeWillChange]
public function getChildren()
{
$children = new self($this->iterator->getChildren(), []);
$children->excludedDirs = $this->excludedDirs;
$children->excludedPattern = $this->excludedPattern;
return $children;
}
}

View file

@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* FileTypeFilterIterator only keeps files, directories, or both.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @extends \FilterIterator<string, \SplFileInfo>
*/
class FileTypeFilterIterator extends \FilterIterator
{
public const ONLY_FILES = 1;
public const ONLY_DIRECTORIES = 2;
private $mode;
/**
* @param \Iterator $iterator The Iterator to filter
* @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES)
*/
public function __construct(\Iterator $iterator, int $mode)
{
$this->mode = $mode;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
$fileinfo = $this->current();
if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) {
return false;
} elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) {
return false;
}
return true;
}
}

View file

@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* FilecontentFilterIterator filters files by their contents using patterns (regexps or strings).
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
*
* @extends MultiplePcreFilterIterator<string, \SplFileInfo>
*/
class FilecontentFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
if (!$this->matchRegexps && !$this->noMatchRegexps) {
return true;
}
$fileinfo = $this->current();
if ($fileinfo->isDir() || !$fileinfo->isReadable()) {
return false;
}
$content = $fileinfo->getContents();
if (!$content) {
return false;
}
return $this->isAccepted($content);
}
/**
* Converts string to regexp if necessary.
*
* @param string $str Pattern: string or regexp
*
* @return string
*/
protected function toRegex(string $str)
{
return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
}
}

View file

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Glob;
/**
* FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string).
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @extends MultiplePcreFilterIterator<string, \SplFileInfo>
*/
class FilenameFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
return $this->isAccepted($this->current()->getFilename());
}
/**
* Converts glob to regexp.
*
* PCRE patterns are left unchanged.
* Glob strings are transformed with Glob::toRegex().
*
* @param string $str Pattern: glob or regexp
*
* @return string
*/
protected function toRegex(string $str)
{
return $this->isRegex($str) ? $str : Glob::toRegex($str);
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*
* @internal
*/
class LazyIterator implements \IteratorAggregate
{
private $iteratorFactory;
public function __construct(callable $iteratorFactory)
{
$this->iteratorFactory = $iteratorFactory;
}
public function getIterator(): \Traversable
{
yield from ($this->iteratorFactory)();
}
}

View file

@ -0,0 +1,117 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings).
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @template-covariant TKey
* @template-covariant TValue
*
* @extends \FilterIterator<TKey, TValue>
*/
abstract class MultiplePcreFilterIterator extends \FilterIterator
{
protected $matchRegexps = [];
protected $noMatchRegexps = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param string[] $matchPatterns An array of patterns that need to match
* @param string[] $noMatchPatterns An array of patterns that need to not match
*/
public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns)
{
foreach ($matchPatterns as $pattern) {
$this->matchRegexps[] = $this->toRegex($pattern);
}
foreach ($noMatchPatterns as $pattern) {
$this->noMatchRegexps[] = $this->toRegex($pattern);
}
parent::__construct($iterator);
}
/**
* Checks whether the string is accepted by the regex filters.
*
* If there is no regexps defined in the class, this method will accept the string.
* Such case can be handled by child classes before calling the method if they want to
* apply a different behavior.
*
* @return bool
*/
protected function isAccepted(string $string)
{
// should at least not match one rule to exclude
foreach ($this->noMatchRegexps as $regex) {
if (preg_match($regex, $string)) {
return false;
}
}
// should at least match one rule
if ($this->matchRegexps) {
foreach ($this->matchRegexps as $regex) {
if (preg_match($regex, $string)) {
return true;
}
}
return false;
}
// If there is no match rules, the file is accepted
return true;
}
/**
* Checks whether the string is a regex.
*
* @return bool
*/
protected function isRegex(string $str)
{
$availableModifiers = 'imsxuADU';
if (\PHP_VERSION_ID >= 80200) {
$availableModifiers .= 'n';
}
if (preg_match('/^(.{3,}?)['.$availableModifiers.']*$/', $str, $m)) {
$start = substr($m[1], 0, 1);
$end = substr($m[1], -1);
if ($start === $end) {
return !preg_match('/[*?[:alnum:] \\\\]/', $start);
}
foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) {
if ($start === $delimiters[0] && $end === $delimiters[1]) {
return true;
}
}
}
return false;
}
/**
* Converts string into regexp.
*
* @return string
*/
abstract protected function toRegex(string $str);
}

View file

@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* PathFilterIterator filters files by path patterns (e.g. some/special/dir).
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
*
* @extends MultiplePcreFilterIterator<string, \SplFileInfo>
*/
class PathFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
$filename = $this->current()->getRelativePathname();
if ('\\' === \DIRECTORY_SEPARATOR) {
$filename = str_replace('\\', '/', $filename);
}
return $this->isAccepted($filename);
}
/**
* Converts strings to regexp.
*
* PCRE patterns are left unchanged.
*
* Default conversion:
* 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/'
*
* Use only / as directory separator (on Windows also).
*
* @param string $str Pattern: regexp or dirname
*
* @return string
*/
protected function toRegex(string $str)
{
return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
}
}

View file

@ -0,0 +1,158 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Exception\AccessDeniedException;
use Symfony\Component\Finder\SplFileInfo;
/**
* Extends the \RecursiveDirectoryIterator to support relative paths.
*
* @author Victor Berchet <victor@suumit.com>
*/
class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
{
/**
* @var bool
*/
private $ignoreUnreadableDirs;
/**
* @var bool
*/
private $ignoreFirstRewind = true;
// these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations
private $rootPath;
private $subPath;
private $directorySeparator = '/';
/**
* @throws \RuntimeException
*/
public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false)
{
if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) {
throw new \RuntimeException('This iterator only support returning current as fileinfo.');
}
parent::__construct($path, $flags);
$this->ignoreUnreadableDirs = $ignoreUnreadableDirs;
$this->rootPath = $path;
if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) {
$this->directorySeparator = \DIRECTORY_SEPARATOR;
}
}
/**
* Return an instance of SplFileInfo with support for relative paths.
*
* @return SplFileInfo
*/
#[\ReturnTypeWillChange]
public function current()
{
// the logic here avoids redoing the same work in all iterations
if (null === $subPathname = $this->subPath) {
$subPathname = $this->subPath = $this->getSubPath();
}
if ('' !== $subPathname) {
$subPathname .= $this->directorySeparator;
}
$subPathname .= $this->getFilename();
$basePath = $this->rootPath;
if ('/' !== $basePath && !str_ends_with($basePath, $this->directorySeparator) && !str_ends_with($basePath, '/')) {
$basePath .= $this->directorySeparator;
}
return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname);
}
/**
* @param bool $allowLinks
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function hasChildren($allowLinks = false)
{
$hasChildren = parent::hasChildren($allowLinks);
if (!$hasChildren || !$this->ignoreUnreadableDirs) {
return $hasChildren;
}
try {
parent::getChildren();
return true;
} catch (\UnexpectedValueException $e) {
// If directory is unreadable and finder is set to ignore it, skip children
return false;
}
}
/**
* @return \RecursiveDirectoryIterator
*
* @throws AccessDeniedException
*/
#[\ReturnTypeWillChange]
public function getChildren()
{
try {
$children = parent::getChildren();
if ($children instanceof self) {
// parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore
$children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs;
// performance optimization to avoid redoing the same work in all children
$children->rootPath = $this->rootPath;
}
return $children;
} catch (\UnexpectedValueException $e) {
throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* @return void
*/
#[\ReturnTypeWillChange]
public function next()
{
$this->ignoreFirstRewind = false;
parent::next();
}
/**
* @return void
*/
#[\ReturnTypeWillChange]
public function rewind()
{
// some streams like FTP are not rewindable, ignore the first rewind after creation,
// as newly created DirectoryIterator does not need to be rewound
if ($this->ignoreFirstRewind) {
$this->ignoreFirstRewind = false;
return;
}
parent::rewind();
}
}

View file

@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Comparator\NumberComparator;
/**
* SizeRangeFilterIterator filters out files that are not in the given size range.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @extends \FilterIterator<string, \SplFileInfo>
*/
class SizeRangeFilterIterator extends \FilterIterator
{
private $comparators = [];
/**
* @param \Iterator<string, \SplFileInfo> $iterator
* @param NumberComparator[] $comparators
*/
public function __construct(\Iterator $iterator, array $comparators)
{
$this->comparators = $comparators;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
$fileinfo = $this->current();
if (!$fileinfo->isFile()) {
return true;
}
$filesize = $fileinfo->getSize();
foreach ($this->comparators as $compare) {
if (!$compare->test($filesize)) {
return false;
}
}
return true;
}
}

View file

@ -0,0 +1,104 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* SortableIterator applies a sort on a given Iterator.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @implements \IteratorAggregate<string, \SplFileInfo>
*/
class SortableIterator implements \IteratorAggregate
{
public const SORT_BY_NONE = 0;
public const SORT_BY_NAME = 1;
public const SORT_BY_TYPE = 2;
public const SORT_BY_ACCESSED_TIME = 3;
public const SORT_BY_CHANGED_TIME = 4;
public const SORT_BY_MODIFIED_TIME = 5;
public const SORT_BY_NAME_NATURAL = 6;
private $iterator;
private $sort;
/**
* @param \Traversable<string, \SplFileInfo> $iterator
* @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback)
*
* @throws \InvalidArgumentException
*/
public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false)
{
$this->iterator = $iterator;
$order = $reverseOrder ? -1 : 1;
if (self::SORT_BY_NAME === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
};
} elseif (self::SORT_BY_NAME_NATURAL === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
};
} elseif (self::SORT_BY_TYPE === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
if ($a->isDir() && $b->isFile()) {
return -$order;
} elseif ($a->isFile() && $b->isDir()) {
return $order;
}
return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
};
} elseif (self::SORT_BY_ACCESSED_TIME === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
return $order * ($a->getATime() - $b->getATime());
};
} elseif (self::SORT_BY_CHANGED_TIME === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
return $order * ($a->getCTime() - $b->getCTime());
};
} elseif (self::SORT_BY_MODIFIED_TIME === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
return $order * ($a->getMTime() - $b->getMTime());
};
} elseif (self::SORT_BY_NONE === $sort) {
$this->sort = $order;
} elseif (\is_callable($sort)) {
$this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort;
} else {
throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.');
}
}
/**
* @return \Traversable<string, \SplFileInfo>
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
if (1 === $this->sort) {
return $this->iterator;
}
$array = iterator_to_array($this->iterator, true);
if (-1 === $this->sort) {
$array = array_reverse($array);
} else {
uasort($array, $this->sort);
}
return new \ArrayIterator($array);
}
}

View file

@ -0,0 +1,151 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Gitignore;
final class VcsIgnoredFilterIterator extends \FilterIterator
{
/**
* @var string
*/
private $baseDir;
/**
* @var array<string, array{0: string, 1: string}|null>
*/
private $gitignoreFilesCache = [];
/**
* @var array<string, bool>
*/
private $ignoredPathsCache = [];
public function __construct(\Iterator $iterator, string $baseDir)
{
$this->baseDir = $this->normalizePath($baseDir);
parent::__construct($iterator);
}
public function accept(): bool
{
$file = $this->current();
$fileRealPath = $this->normalizePath($file->getRealPath());
return !$this->isIgnored($fileRealPath);
}
private function isIgnored(string $fileRealPath): bool
{
if (is_dir($fileRealPath) && !str_ends_with($fileRealPath, '/')) {
$fileRealPath .= '/';
}
if (isset($this->ignoredPathsCache[$fileRealPath])) {
return $this->ignoredPathsCache[$fileRealPath];
}
$ignored = false;
foreach ($this->parentsDirectoryDownward($fileRealPath) as $parentDirectory) {
if ($this->isIgnored($parentDirectory)) {
// rules in ignored directories are ignored, no need to check further.
break;
}
$fileRelativePath = substr($fileRealPath, \strlen($parentDirectory) + 1);
if (null === $regexps = $this->readGitignoreFile("{$parentDirectory}/.gitignore")) {
continue;
}
[$exclusionRegex, $inclusionRegex] = $regexps;
if (preg_match($exclusionRegex, $fileRelativePath)) {
$ignored = true;
continue;
}
if (preg_match($inclusionRegex, $fileRelativePath)) {
$ignored = false;
}
}
return $this->ignoredPathsCache[$fileRealPath] = $ignored;
}
/**
* @return list<string>
*/
private function parentsDirectoryDownward(string $fileRealPath): array
{
$parentDirectories = [];
$parentDirectory = $fileRealPath;
while (true) {
$newParentDirectory = \dirname($parentDirectory);
// dirname('/') = '/'
if ($newParentDirectory === $parentDirectory) {
break;
}
$parentDirectory = $newParentDirectory;
if (0 !== strpos($parentDirectory, $this->baseDir)) {
break;
}
$parentDirectories[] = $parentDirectory;
}
return array_reverse($parentDirectories);
}
/**
* @return array{0: string, 1: string}|null
*/
private function readGitignoreFile(string $path): ?array
{
if (\array_key_exists($path, $this->gitignoreFilesCache)) {
return $this->gitignoreFilesCache[$path];
}
if (!file_exists($path)) {
return $this->gitignoreFilesCache[$path] = null;
}
if (!is_file($path) || !is_readable($path)) {
throw new \RuntimeException("The \"ignoreVCSIgnored\" option cannot be used by the Finder as the \"{$path}\" file is not readable.");
}
$gitignoreFileContent = file_get_contents($path);
return $this->gitignoreFilesCache[$path] = [
Gitignore::toRegex($gitignoreFileContent),
Gitignore::toRegexMatchingNegatedPatterns($gitignoreFileContent),
];
}
private function normalizePath(string $path): string
{
if ('\\' === \DIRECTORY_SEPARATOR) {
return str_replace('\\', '/', $path);
}
return $path;
}
}

19
vendor/symfony/finder/LICENSE vendored Normal file
View file

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

14
vendor/symfony/finder/README.md vendored Normal file
View file

@ -0,0 +1,14 @@
Finder Component
================
The Finder component finds files and directories via an intuitive fluent
interface.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/finder.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

88
vendor/symfony/finder/SplFileInfo.php vendored Normal file
View file

@ -0,0 +1,88 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Extends \SplFileInfo to support relative paths.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SplFileInfo extends \SplFileInfo
{
private $relativePath;
private $relativePathname;
/**
* @param string $file The file name
* @param string $relativePath The relative path
* @param string $relativePathname The relative path name
*/
public function __construct(string $file, string $relativePath, string $relativePathname)
{
parent::__construct($file);
$this->relativePath = $relativePath;
$this->relativePathname = $relativePathname;
}
/**
* Returns the relative path.
*
* This path does not contain the file name.
*
* @return string
*/
public function getRelativePath()
{
return $this->relativePath;
}
/**
* Returns the relative path name.
*
* This path contains the file name.
*
* @return string
*/
public function getRelativePathname()
{
return $this->relativePathname;
}
public function getFilenameWithoutExtension(): string
{
$filename = $this->getFilename();
return pathinfo($filename, \PATHINFO_FILENAME);
}
/**
* Returns the contents of the file.
*
* @return string
*
* @throws \RuntimeException
*/
public function getContents()
{
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
try {
$content = file_get_contents($this->getPathname());
} finally {
restore_error_handler();
}
if (false === $content) {
throw new \RuntimeException($error);
}
return $content;
}
}

30
vendor/symfony/finder/composer.json vendored Normal file
View file

@ -0,0 +1,30 @@
{
"name": "symfony/finder",
"type": "library",
"description": "Finds files and directories via an intuitive fluent interface",
"keywords": [],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/polyfill-php80": "^1.16"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Finder\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev"
}

View file

@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

View file

@ -0,0 +1,5 @@
CHANGELOG
=========
The changelog is maintained for all Symfony contracts at the following URL:
https://github.com/symfony/contracts/blob/main/CHANGELOG.md

View file

@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
/**
* The interface of chunks returned by ResponseStreamInterface::current().
*
* When the chunk is first, last or timeout, the content MUST be empty.
* When an unchecked timeout or a network error occurs, a TransportExceptionInterface
* MUST be thrown by the destructor unless one was already thrown by another method.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface ChunkInterface
{
/**
* Tells when the idle timeout has been reached.
*
* @throws TransportExceptionInterface on a network error
*/
public function isTimeout(): bool;
/**
* Tells when headers just arrived.
*
* @throws TransportExceptionInterface on a network error or when the idle timeout is reached
*/
public function isFirst(): bool;
/**
* Tells when the body just completed.
*
* @throws TransportExceptionInterface on a network error or when the idle timeout is reached
*/
public function isLast(): bool;
/**
* Returns a [status code, headers] tuple when a 1xx status code was just received.
*
* @throws TransportExceptionInterface on a network error or when the idle timeout is reached
*/
public function getInformationalStatus(): ?array;
/**
* Returns the content of the response chunk.
*
* @throws TransportExceptionInterface on a network error or when the idle timeout is reached
*/
public function getContent(): string;
/**
* Returns the offset of the chunk in the response body.
*/
public function getOffset(): int;
/**
* In case of error, returns the message that describes it.
*/
public function getError(): ?string;
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient\Exception;
/**
* When a 4xx response is returned.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface ClientExceptionInterface extends HttpExceptionInterface
{
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient\Exception;
/**
* When a content-type cannot be decoded to the expected representation.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface DecodingExceptionInterface extends ExceptionInterface
{
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\HttpClient\Exception;
/**
* The base interface for all exceptions in the contract.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface ExceptionInterface extends \Throwable
{
}

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