This commit is contained in:
cutemeli
2025-12-22 10:35:30 +00:00
parent 0bfc6c8425
commit 5ce7ca2c5d
38927 changed files with 0 additions and 4594700 deletions

View File

@@ -1,25 +0,0 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit73f57512af9eb93ce8fb2b143187fe9d::getLoader();

View File

@@ -1,579 +0,0 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir;
// PSR-4
/**
* @var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* List of PSR-0 prefixes
*
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array<string, array<string, list<string>>>
*/
private $prefixesPsr0 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
/**
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return list<string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return list<string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return array<string, string> Array of classname => path
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array<string, string> $classMap Class to filename map
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
$paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
$paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
$paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
$paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders keyed by their corresponding vendor directories.
*
* @return array<string, self>
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}

View File

@@ -1,313 +0,0 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PerformanceBooster\Composer;
use PerformanceBooster\Composer\Autoload\ClassLoader;
use PerformanceBooster\Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = \array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return \array_keys(\array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = \true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === \false;
}
}
return \false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (\array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = \array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (\array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = \array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (\array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = \array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return \implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@\trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', \E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (\substr(__DIR__, -8, 1) !== 'C') {
self::$installed = (include __DIR__ . '/installed.php');
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = \method_exists('PerformanceBooster\\Composer\\Autoload\\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (\is_file($vendorDir . '/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = (require $vendorDir . '/composer/installed.php');
$installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && \strtr($vendorDir . '/composer', '\\', '/') === \strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[\count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (\substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = (require __DIR__ . '/installed.php');
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array()) {
$installed[] = self::$installed;
}
return $installed;
}
}

View File

@@ -1,21 +0,0 @@
Copyright (c) Nils Adermann, Jordi Boggiano
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

@@ -1,10 +0,0 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname(dirname(dirname($vendorDir)));
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);

View File

@@ -1,13 +0,0 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname(dirname(dirname($vendorDir)));
return array(
'ZXh0LXBlcmZvcm1hbmNlLWJvb3N0ZXI=6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'ZXh0LXBlcmZvcm1hbmNlLWJvb3N0ZXI=b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'ZXh0LXBlcmZvcm1hbmNlLWJvb3N0ZXI=253c157292f75eb38082b5acb06f3f01' => $vendorDir . '/nikic/fast-route/src/functions.php',
'ZXh0LXBlcmZvcm1hbmNlLWJvb3N0ZXI=b33e3d135e5d9e47d845c576147bda89' => $vendorDir . '/php-di/php-di/src/functions.php',
);

View File

@@ -1,9 +0,0 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname(dirname(dirname($vendorDir)));
return array(
);

View File

@@ -1,31 +0,0 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname(dirname(dirname($vendorDir)));
return array(
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'Psr\\Http\\Server\\' => array($vendorDir . '/psr/http-server-handler/src', $vendorDir . '/psr/http-server-middleware/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'Plesk\\SDK\\TypedPleskSdk\\' => array($vendorDir . '/plesk/typedplesksdk/src'),
'PleskX\\' => array($vendorDir . '/plesk/api-php-lib/src'),
'PleskExt\\PerformanceBooster\\' => array($baseDir . '/modules/performance-booster/library', $baseDir . '/src/plib/library'),
'PerformanceBooster\\Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
'PerformanceBooster\\Symfony\\Component\\TypeInfo\\' => array($vendorDir . '/symfony/type-info'),
'PerformanceBooster\\Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'),
'PerformanceBooster\\Symfony\\Component\\Serializer\\' => array($vendorDir . '/symfony/serializer'),
'PerformanceBooster\\Symfony\\Component\\PropertyInfo\\' => array($vendorDir . '/symfony/property-info'),
'PerformanceBooster\\Symfony\\Component\\PropertyAccess\\' => array($vendorDir . '/symfony/property-access'),
'PerformanceBooster\\Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
'PerformanceBooster\\Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
'PerformanceBooster\\Slim\\' => array($vendorDir . '/slim/slim/Slim'),
'PerformanceBooster\\Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'),
'PerformanceBooster\\Invoker\\' => array($vendorDir . '/php-di/invoker/src'),
'PerformanceBooster\\FastRoute\\' => array($vendorDir . '/nikic/fast-route/src'),
'PerformanceBooster\\DI\\' => array($vendorDir . '/php-di/php-di/src'),
'Nyholm\\Psr7\\' => array($vendorDir . '/nyholm/psr7/src'),
'Nyholm\\Psr7Server\\' => array($vendorDir . '/nyholm/psr7-server/src'),
);

View File

@@ -1,50 +0,0 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit73f57512af9eb93ce8fb2b143187fe9d
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit73f57512af9eb93ce8fb2b143187fe9d', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit73f57512af9eb93ce8fb2b143187fe9d', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit73f57512af9eb93ce8fb2b143187fe9d::getInitializer($loader));
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInit73f57512af9eb93ce8fb2b143187fe9d::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}, null, null);
foreach ($filesToLoad as $fileIdentifier => $file) {
$requireFile($fileIdentifier, $file);
}
return $loader;
}
}

View File

@@ -1,154 +0,0 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit73f57512af9eb93ce8fb2b143187fe9d
{
public static $files = array (
'ZXh0LXBlcmZvcm1hbmNlLWJvb3N0ZXI=6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'ZXh0LXBlcmZvcm1hbmNlLWJvb3N0ZXI=b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
'ZXh0LXBlcmZvcm1hbmNlLWJvb3N0ZXI=253c157292f75eb38082b5acb06f3f01' => __DIR__ . '/..' . '/nikic/fast-route/src/functions.php',
'ZXh0LXBlcmZvcm1hbmNlLWJvb3N0ZXI=b33e3d135e5d9e47d845c576147bda89' => __DIR__ . '/..' . '/php-di/php-di/src/functions.php',
);
public static $prefixLengthsPsr4 = array (
'P' =>
array (
'Psr\\Log\\' => 8,
'Psr\\Http\\Server\\' => 16,
'Psr\\Http\\Message\\' => 17,
'Psr\\Container\\' => 14,
'Plesk\\SDK\\TypedPleskSdk\\' => 24,
'PleskX\\' => 7,
'PleskExt\\PerformanceBooster\\' => 28,
'PerformanceBooster\\Symfony\\Contracts\\Service\\' => 45,
'PerformanceBooster\\Symfony\\Component\\TypeInfo\\' => 46,
'PerformanceBooster\\Symfony\\Component\\String\\' => 44,
'PerformanceBooster\\Symfony\\Component\\Serializer\\' => 48,
'PerformanceBooster\\Symfony\\Component\\PropertyInfo\\' => 50,
'PerformanceBooster\\Symfony\\Component\\PropertyAccess\\' => 52,
'PerformanceBooster\\Symfony\\Component\\Process\\' => 45,
'PerformanceBooster\\Symfony\\Component\\Console\\' => 45,
'PerformanceBooster\\Slim\\' => 24,
'PerformanceBooster\\Laravel\\SerializableClosure\\' => 47,
'PerformanceBooster\\Invoker\\' => 27,
'PerformanceBooster\\FastRoute\\' => 29,
'PerformanceBooster\\DI\\' => 22,
),
'N' =>
array (
'Nyholm\\Psr7\\' => 12,
'Nyholm\\Psr7Server\\' => 18,
),
);
public static $prefixDirsPsr4 = array (
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
),
'Psr\\Http\\Server\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-server-handler/src',
1 => __DIR__ . '/..' . '/psr/http-server-middleware/src',
),
'Psr\\Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-factory/src',
1 => __DIR__ . '/..' . '/psr/http-message/src',
),
'Psr\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/psr/container/src',
),
'Plesk\\SDK\\TypedPleskSdk\\' =>
array (
0 => __DIR__ . '/..' . '/plesk/typedplesksdk/src',
),
'PleskX\\' =>
array (
0 => __DIR__ . '/..' . '/plesk/api-php-lib/src',
),
'PleskExt\\PerformanceBooster\\' =>
array (
0 => __DIR__ . '/../../../..' . '/modules/performance-booster/library',
1 => __DIR__ . '/../../../..' . '/src/plib/library',
),
'PerformanceBooster\\Symfony\\Contracts\\Service\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/service-contracts',
),
'PerformanceBooster\\Symfony\\Component\\TypeInfo\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/type-info',
),
'PerformanceBooster\\Symfony\\Component\\String\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/string',
),
'PerformanceBooster\\Symfony\\Component\\Serializer\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/serializer',
),
'PerformanceBooster\\Symfony\\Component\\PropertyInfo\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/property-info',
),
'PerformanceBooster\\Symfony\\Component\\PropertyAccess\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/property-access',
),
'PerformanceBooster\\Symfony\\Component\\Process\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/process',
),
'PerformanceBooster\\Symfony\\Component\\Console\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/console',
),
'PerformanceBooster\\Slim\\' =>
array (
0 => __DIR__ . '/..' . '/slim/slim/Slim',
),
'PerformanceBooster\\Laravel\\SerializableClosure\\' =>
array (
0 => __DIR__ . '/..' . '/laravel/serializable-closure/src',
),
'PerformanceBooster\\Invoker\\' =>
array (
0 => __DIR__ . '/..' . '/php-di/invoker/src',
),
'PerformanceBooster\\FastRoute\\' =>
array (
0 => __DIR__ . '/..' . '/nikic/fast-route/src',
),
'PerformanceBooster\\DI\\' =>
array (
0 => __DIR__ . '/..' . '/php-di/php-di/src',
),
'Nyholm\\Psr7\\' =>
array (
0 => __DIR__ . '/..' . '/nyholm/psr7/src',
),
'Nyholm\\Psr7Server\\' =>
array (
0 => __DIR__ . '/..' . '/nyholm/psr7-server/src',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit73f57512af9eb93ce8fb2b143187fe9d::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit73f57512af9eb93ce8fb2b143187fe9d::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit73f57512af9eb93ce8fb2b143187fe9d::$classMap;
}, null, ClassLoader::class);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,26 +0,0 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 80206)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.2.6". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

View File

@@ -1,19 +0,0 @@
<?php
namespace PerformanceBooster\Laravel\SerializableClosure\Contracts;
interface Serializable
{
/**
* Resolve the closure with the given arguments.
*
* @return mixed
*/
public function __invoke();
/**
* Gets the closure that got serialized/unserialized.
*
* @return \Closure
*/
public function getClosure();
}

View File

@@ -1,21 +0,0 @@
<?php
namespace PerformanceBooster\Laravel\SerializableClosure\Contracts;
interface Signer
{
/**
* Sign the given serializable.
*
* @param string $serializable
* @return array
*/
public function sign($serializable);
/**
* Verify the given signature.
*
* @param array $signature
* @return bool
*/
public function verify($signature);
}

View File

@@ -1,18 +0,0 @@
<?php
namespace PerformanceBooster\Laravel\SerializableClosure\Exceptions;
use Exception;
class InvalidSignatureException extends Exception
{
/**
* Create a new exception instance.
*
* @param string $message
* @return void
*/
public function __construct($message = 'Your serialized closure might have been modified or it\'s unsafe to be unserialized.')
{
parent::__construct($message);
}
}

View File

@@ -1,18 +0,0 @@
<?php
namespace PerformanceBooster\Laravel\SerializableClosure\Exceptions;
use Exception;
class MissingSecretKeyException extends Exception
{
/**
* Create a new exception instance.
*
* @param string $message
* @return void
*/
public function __construct($message = 'No serializable closure secret key has been specified.')
{
parent::__construct($message);
}
}

View File

@@ -1,18 +0,0 @@
<?php
namespace PerformanceBooster\Laravel\SerializableClosure\Exceptions;
use Exception;
class PhpVersionNotSupportedException extends Exception
{
/**
* Create a new exception instance.
*
* @param string $message
* @return void
*/
public function __construct($message = 'PHP 7.3 is not supported.')
{
parent::__construct($message);
}
}

View File

@@ -1,109 +0,0 @@
<?php
namespace PerformanceBooster\Laravel\SerializableClosure;
use Closure;
use PerformanceBooster\Laravel\SerializableClosure\Exceptions\InvalidSignatureException;
use PerformanceBooster\Laravel\SerializableClosure\Serializers\Signed;
use PerformanceBooster\Laravel\SerializableClosure\Signers\Hmac;
class SerializableClosure
{
/**
* The closure's serializable.
*
* @var \Laravel\SerializableClosure\Contracts\Serializable
*/
protected $serializable;
/**
* Creates a new serializable closure instance.
*
* @param \Closure $closure
* @return void
*/
public function __construct(Closure $closure)
{
$this->serializable = Serializers\Signed::$signer ? new Serializers\Signed($closure) : new Serializers\Native($closure);
}
/**
* Resolve the closure with the given arguments.
*
* @return mixed
*/
public function __invoke()
{
return \call_user_func_array($this->serializable, \func_get_args());
}
/**
* Gets the closure.
*
* @return \Closure
*/
public function getClosure()
{
return $this->serializable->getClosure();
}
/**
* Create a new unsigned serializable closure instance.
*
* @param Closure $closure
* @return \Laravel\SerializableClosure\UnsignedSerializableClosure
*/
public static function unsigned(Closure $closure)
{
return new UnsignedSerializableClosure($closure);
}
/**
* Sets the serializable closure secret key.
*
* @param string|null $secret
* @return void
*/
public static function setSecretKey($secret)
{
Serializers\Signed::$signer = $secret ? new Hmac($secret) : null;
}
/**
* Sets the serializable closure secret key.
*
* @param \Closure|null $transformer
* @return void
*/
public static function transformUseVariablesUsing($transformer)
{
Serializers\Native::$transformUseVariables = $transformer;
}
/**
* Sets the serializable closure secret key.
*
* @param \Closure|null $resolver
* @return void
*/
public static function resolveUseVariablesUsing($resolver)
{
Serializers\Native::$resolveUseVariables = $resolver;
}
/**
* Get the serializable representation of the closure.
*
* @return array{serializable: \Laravel\SerializableClosure\Serializers\Signed|\Laravel\SerializableClosure\Contracts\Serializable}
*/
public function __serialize()
{
return ['serializable' => $this->serializable];
}
/**
* Restore the closure after serialization.
*
* @param array{serializable: \Laravel\SerializableClosure\Serializers\Signed|\Laravel\SerializableClosure\Contracts\Serializable} $data
* @return void
*
* @throws \Laravel\SerializableClosure\Exceptions\InvalidSignatureException
*/
public function __unserialize($data)
{
if (Signed::$signer && !$data['serializable'] instanceof Signed) {
throw new InvalidSignatureException();
}
$this->serializable = $data['serializable'];
}
}

View File

@@ -1,407 +0,0 @@
<?php
namespace PerformanceBooster\Laravel\SerializableClosure\Serializers;
use Closure;
use DateTimeInterface;
use PerformanceBooster\Laravel\SerializableClosure\Contracts\Serializable;
use PerformanceBooster\Laravel\SerializableClosure\SerializableClosure;
use PerformanceBooster\Laravel\SerializableClosure\Support\ClosureScope;
use PerformanceBooster\Laravel\SerializableClosure\Support\ClosureStream;
use PerformanceBooster\Laravel\SerializableClosure\Support\ReflectionClosure;
use PerformanceBooster\Laravel\SerializableClosure\Support\SelfReference;
use PerformanceBooster\Laravel\SerializableClosure\UnsignedSerializableClosure;
use ReflectionObject;
use ReflectionProperty;
use UnitEnum;
class Native implements Serializable
{
/**
* Transform the use variables before serialization.
*
* @var \Closure|null
*/
public static $transformUseVariables;
/**
* Resolve the use variables after unserialization.
*
* @var \Closure|null
*/
public static $resolveUseVariables;
/**
* The closure to be serialized/unserialized.
*
* @var \Closure
*/
protected $closure;
/**
* The closure's reflection.
*
* @var \Laravel\SerializableClosure\Support\ReflectionClosure|null
*/
protected $reflector;
/**
* The closure's code.
*
* @var array|null
*/
protected $code;
/**
* The closure's reference.
*
* @var string
*/
protected $reference;
/**
* The closure's scope.
*
* @var \Laravel\SerializableClosure\Support\ClosureScope|null
*/
protected $scope;
/**
* The "key" that marks an array as recursive.
*/
const ARRAY_RECURSIVE_KEY = 'LARAVEL_SERIALIZABLE_RECURSIVE_KEY';
/**
* Creates a new serializable closure instance.
*
* @param \Closure $closure
* @return void
*/
public function __construct(Closure $closure)
{
$this->closure = $closure;
}
/**
* Resolve the closure with the given arguments.
*
* @return mixed
*/
public function __invoke()
{
return \call_user_func_array($this->closure, \func_get_args());
}
/**
* Gets the closure.
*
* @return \Closure
*/
public function getClosure()
{
return $this->closure;
}
/**
* Get the serializable representation of the closure.
*
* @return array
*/
public function __serialize()
{
if ($this->scope === null) {
$this->scope = new ClosureScope();
$this->scope->toSerialize++;
}
$this->scope->serializations++;
$scope = $object = null;
$reflector = $this->getReflector();
if ($reflector->isBindingRequired()) {
$object = $reflector->getClosureThis();
static::wrapClosures($object, $this->scope);
}
if ($scope = $reflector->getClosureScopeClass()) {
$scope = $scope->name;
}
$this->reference = \spl_object_hash($this->closure);
$this->scope[$this->closure] = $this;
$use = $reflector->getUseVariables();
if (static::$transformUseVariables) {
$use = \call_user_func(static::$transformUseVariables, $reflector->getUseVariables());
}
$code = $reflector->getCode();
$this->mapByReference($use);
$data = ['use' => $use, 'function' => $code, 'scope' => $scope, 'this' => $object, 'self' => $this->reference];
if (!--$this->scope->serializations && !--$this->scope->toSerialize) {
$this->scope = null;
}
return $data;
}
/**
* Restore the closure after serialization.
*
* @param array $data
* @return void
*/
public function __unserialize($data)
{
ClosureStream::register();
$this->code = $data;
unset($data);
$this->code['objects'] = [];
if ($this->code['use']) {
$this->scope = new ClosureScope();
if (static::$resolveUseVariables) {
$this->code['use'] = \call_user_func(static::$resolveUseVariables, $this->code['use']);
}
$this->mapPointers($this->code['use']);
\extract($this->code['use'], \EXTR_OVERWRITE | \EXTR_REFS);
$this->scope = null;
}
$this->closure = (include ClosureStream::STREAM_PROTO . '://' . $this->code['function']);
if ($this->code['this'] === $this) {
$this->code['this'] = null;
}
$this->closure = $this->closure->bindTo($this->code['this'], $this->code['scope']);
if (!empty($this->code['objects'])) {
foreach ($this->code['objects'] as $item) {
$item['property']->setValue($item['instance'], $item['object']->getClosure());
}
}
$this->code = $this->code['function'];
}
/**
* Ensures the given closures are serializable.
*
* @param mixed $data
* @param \Laravel\SerializableClosure\Support\ClosureScope $storage
* @return void
*/
public static function wrapClosures(&$data, $storage)
{
if ($data instanceof Closure) {
$data = new static($data);
} elseif (\is_array($data)) {
if (isset($data[self::ARRAY_RECURSIVE_KEY])) {
return;
}
$data[self::ARRAY_RECURSIVE_KEY] = \true;
foreach ($data as $key => &$value) {
if ($key === self::ARRAY_RECURSIVE_KEY) {
continue;
}
static::wrapClosures($value, $storage);
}
unset($value);
unset($data[self::ARRAY_RECURSIVE_KEY]);
} elseif ($data instanceof \stdClass) {
if (isset($storage[$data])) {
$data = $storage[$data];
return;
}
$data = $storage[$data] = clone $data;
foreach ($data as &$value) {
static::wrapClosures($value, $storage);
}
unset($value);
} elseif (\is_object($data) && !$data instanceof static && !$data instanceof UnitEnum) {
if (isset($storage[$data])) {
$data = $storage[$data];
return;
}
$instance = $data;
$reflection = new ReflectionObject($instance);
if (!$reflection->isUserDefined()) {
$storage[$instance] = $data;
return;
}
$storage[$instance] = $data = $reflection->newInstanceWithoutConstructor();
do {
if (!$reflection->isUserDefined()) {
break;
}
foreach ($reflection->getProperties() as $property) {
if ($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()) {
continue;
}
$property->setAccessible(\true);
if (!$property->isInitialized($instance)) {
continue;
}
$value = $property->getValue($instance);
if (\is_array($value) || \is_object($value)) {
static::wrapClosures($value, $storage);
}
$property->setValue($data, $value);
}
} while ($reflection = $reflection->getParentClass());
}
}
/**
* Gets the closure's reflector.
*
* @return \Laravel\SerializableClosure\Support\ReflectionClosure
*/
public function getReflector()
{
if ($this->reflector === null) {
$this->code = null;
$this->reflector = new ReflectionClosure($this->closure);
}
return $this->reflector;
}
/**
* Internal method used to map closure pointers.
*
* @param mixed $data
* @return void
*/
protected function mapPointers(&$data)
{
$scope = $this->scope;
if ($data instanceof static) {
$data =& $data->closure;
} elseif (\is_array($data)) {
if (isset($data[self::ARRAY_RECURSIVE_KEY])) {
return;
}
$data[self::ARRAY_RECURSIVE_KEY] = \true;
foreach ($data as $key => &$value) {
if ($key === self::ARRAY_RECURSIVE_KEY) {
continue;
} elseif ($value instanceof static) {
$data[$key] =& $value->closure;
} elseif ($value instanceof SelfReference && $value->hash === $this->code['self']) {
$data[$key] =& $this->closure;
} else {
$this->mapPointers($value);
}
}
unset($value);
unset($data[self::ARRAY_RECURSIVE_KEY]);
} elseif ($data instanceof \stdClass) {
if (isset($scope[$data])) {
return;
}
$scope[$data] = \true;
foreach ($data as $key => &$value) {
if ($value instanceof SelfReference && $value->hash === $this->code['self']) {
$data->{$key} =& $this->closure;
} elseif (\is_array($value) || \is_object($value)) {
$this->mapPointers($value);
}
}
unset($value);
} elseif (\is_object($data) && !$data instanceof Closure) {
if (isset($scope[$data])) {
return;
}
$scope[$data] = \true;
$reflection = new ReflectionObject($data);
do {
if (!$reflection->isUserDefined()) {
break;
}
foreach ($reflection->getProperties() as $property) {
if ($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()) {
continue;
}
$property->setAccessible(\true);
if (!$property->isInitialized($data) || $property->isReadOnly()) {
continue;
}
$item = $property->getValue($data);
if ($item instanceof SerializableClosure || $item instanceof UnsignedSerializableClosure || $item instanceof SelfReference && $item->hash === $this->code['self']) {
$this->code['objects'][] = ['instance' => $data, 'property' => $property, 'object' => $item instanceof SelfReference ? $this : $item];
} elseif (\is_array($item) || \is_object($item)) {
$this->mapPointers($item);
$property->setValue($data, $item);
}
}
} while ($reflection = $reflection->getParentClass());
}
}
/**
* Internal method used to map closures by reference.
*
* @param mixed $data
* @return void
*/
protected function mapByReference(&$data)
{
if ($data instanceof Closure) {
if ($data === $this->closure) {
$data = new SelfReference($this->reference);
return;
}
if (isset($this->scope[$data])) {
$data = $this->scope[$data];
return;
}
$instance = new static($data);
$instance->scope = $this->scope;
$data = $this->scope[$data] = $instance;
} elseif (\is_array($data)) {
if (isset($data[self::ARRAY_RECURSIVE_KEY])) {
return;
}
$data[self::ARRAY_RECURSIVE_KEY] = \true;
foreach ($data as $key => &$value) {
if ($key === self::ARRAY_RECURSIVE_KEY) {
continue;
}
$this->mapByReference($value);
}
unset($value);
unset($data[self::ARRAY_RECURSIVE_KEY]);
} elseif ($data instanceof \stdClass) {
if (isset($this->scope[$data])) {
$data = $this->scope[$data];
return;
}
$instance = $data;
$this->scope[$instance] = $data = clone $data;
foreach ($data as &$value) {
$this->mapByReference($value);
}
unset($value);
} elseif (\is_object($data) && !$data instanceof SerializableClosure && !$data instanceof UnsignedSerializableClosure) {
if (isset($this->scope[$data])) {
$data = $this->scope[$data];
return;
}
$instance = $data;
if ($data instanceof DateTimeInterface) {
$this->scope[$instance] = $data;
return;
}
if ($data instanceof UnitEnum) {
$this->scope[$instance] = $data;
return;
}
$reflection = new ReflectionObject($data);
if (!$reflection->isUserDefined()) {
$this->scope[$instance] = $data;
return;
}
$this->scope[$instance] = $data = $reflection->newInstanceWithoutConstructor();
do {
if (!$reflection->isUserDefined()) {
break;
}
foreach ($reflection->getProperties() as $property) {
if ($property->isStatic() || !$property->getDeclaringClass()->isUserDefined() || $this->isVirtualProperty($property)) {
continue;
}
$property->setAccessible(\true);
if (!$property->isInitialized($instance) || $property->isReadOnly() && $property->class !== $reflection->name) {
continue;
}
$value = $property->getValue($instance);
if (\is_array($value) || \is_object($value)) {
$this->mapByReference($value);
}
$property->setValue($data, $value);
}
} while ($reflection = $reflection->getParentClass());
}
}
/**
* Determine is virtual property.
*
* @param \ReflectionProperty $property
* @return bool
*/
protected function isVirtualProperty(ReflectionProperty $property) : bool
{
return \method_exists($property, 'isVirtual') && $property->isVirtual();
}
}

View File

@@ -1,79 +0,0 @@
<?php
namespace PerformanceBooster\Laravel\SerializableClosure\Serializers;
use PerformanceBooster\Laravel\SerializableClosure\Contracts\Serializable;
use PerformanceBooster\Laravel\SerializableClosure\Exceptions\InvalidSignatureException;
use PerformanceBooster\Laravel\SerializableClosure\Exceptions\MissingSecretKeyException;
class Signed implements Serializable
{
/**
* The signer that will sign and verify the closure's signature.
*
* @var \Laravel\SerializableClosure\Contracts\Signer|null
*/
public static $signer;
/**
* The closure to be serialized/unserialized.
*
* @var \Closure
*/
protected $closure;
/**
* Creates a new serializable closure instance.
*
* @param \Closure $closure
* @return void
*/
public function __construct($closure)
{
$this->closure = $closure;
}
/**
* Resolve the closure with the given arguments.
*
* @return mixed
*/
public function __invoke()
{
return \call_user_func_array($this->closure, \func_get_args());
}
/**
* Gets the closure.
*
* @return \Closure
*/
public function getClosure()
{
return $this->closure;
}
/**
* Get the serializable representation of the closure.
*
* @return array
*/
public function __serialize()
{
if (!static::$signer) {
throw new MissingSecretKeyException();
}
return static::$signer->sign(\serialize(new Native($this->closure)));
}
/**
* Restore the closure after serialization.
*
* @param array{serializable: string, hash: string} $signature
* @return void
*
* @throws \Laravel\SerializableClosure\Exceptions\InvalidSignatureException
*/
public function __unserialize($signature)
{
if (static::$signer && !static::$signer->verify($signature)) {
throw new InvalidSignatureException();
}
/** @var \Laravel\SerializableClosure\Contracts\Serializable $serializable */
$serializable = \unserialize($signature['serializable']);
$this->closure = $serializable->getClosure();
}
}

View File

@@ -1,44 +0,0 @@
<?php
namespace PerformanceBooster\Laravel\SerializableClosure\Signers;
use PerformanceBooster\Laravel\SerializableClosure\Contracts\Signer;
class Hmac implements Signer
{
/**
* The secret key.
*
* @var string
*/
protected $secret;
/**
* Creates a new signer instance.
*
* @param string $secret
* @return void
*/
public function __construct($secret)
{
$this->secret = $secret;
}
/**
* Sign the given serializable.
*
* @param string $serialized
* @return array
*/
public function sign($serialized)
{
return ['serializable' => $serialized, 'hash' => \base64_encode(\hash_hmac('sha256', $serialized, $this->secret, \true))];
}
/**
* Verify the given signature.
*
* @param array{serializable: string, hash: string} $signature
* @return bool
*/
public function verify($signature)
{
return \hash_equals(\base64_encode(\hash_hmac('sha256', $signature['serializable'], $this->secret, \true)), $signature['hash']);
}
}

View File

@@ -1,20 +0,0 @@
<?php
namespace PerformanceBooster\Laravel\SerializableClosure\Support;
use SplObjectStorage;
class ClosureScope extends SplObjectStorage
{
/**
* The number of serializations in current scope.
*
* @var int
*/
public $serializations = 0;
/**
* The number of closures that have to be serialized.
*
* @var int
*/
public $toSerialize = 0;
}

View File

@@ -1,159 +0,0 @@
<?php
namespace PerformanceBooster\Laravel\SerializableClosure\Support;
#[\AllowDynamicProperties]
class ClosureStream
{
/**
* The stream protocol.
*
* @var string
*/
const STREAM_PROTO = 'laravel-serializable-closure';
/**
* Checks if this stream is registered.
*
* @var bool
*/
protected static $isRegistered = \false;
/**
* The stream content.
*
* @var string
*/
protected $content;
/**
* The stream content.
*
* @var int
*/
protected $length;
/**
* The stream pointer.
*
* @var int
*/
protected $pointer = 0;
/**
* Opens file or URL.
*
* @param string $path
* @param string $mode
* @param string $options
* @param string|null $opened_path
* @return bool
*/
public function stream_open($path, $mode, $options, &$opened_path)
{
$this->content = "<?php\nreturn " . \substr($path, \strlen(static::STREAM_PROTO . '://')) . ';';
$this->length = \strlen($this->content);
return \true;
}
/**
* Read from stream.
*
* @param int $count
* @return string
*/
public function stream_read($count)
{
$value = \substr($this->content, $this->pointer, $count);
$this->pointer += $count;
return $value;
}
/**
* Tests for end-of-file on a file pointer.
*
* @return bool
*/
public function stream_eof()
{
return $this->pointer >= $this->length;
}
/**
* Change stream options.
*
* @param int $option
* @param int $arg1
* @param int $arg2
* @return bool
*/
public function stream_set_option($option, $arg1, $arg2)
{
return \false;
}
/**
* Retrieve information about a file resource.
*
* @return array|bool
*/
public function stream_stat()
{
$stat = \stat(__FILE__);
// @phpstan-ignore-next-line
$stat[7] = $stat['size'] = $this->length;
return $stat;
}
/**
* Retrieve information about a file.
*
* @param string $path
* @param int $flags
* @return array|bool
*/
public function url_stat($path, $flags)
{
$stat = \stat(__FILE__);
// @phpstan-ignore-next-line
$stat[7] = $stat['size'] = $this->length;
return $stat;
}
/**
* Seeks to specific location in a stream.
*
* @param int $offset
* @param int $whence
* @return bool
*/
public function stream_seek($offset, $whence = \SEEK_SET)
{
$crt = $this->pointer;
switch ($whence) {
case \SEEK_SET:
$this->pointer = $offset;
break;
case \SEEK_CUR:
$this->pointer += $offset;
break;
case \SEEK_END:
$this->pointer = $this->length + $offset;
break;
}
if ($this->pointer < 0 || $this->pointer >= $this->length) {
$this->pointer = $crt;
return \false;
}
return \true;
}
/**
* Retrieve the current position of a stream.
*
* @return int
*/
public function stream_tell()
{
return $this->pointer;
}
/**
* Registers the stream.
*
* @return void
*/
public static function register()
{
if (!static::$isRegistered) {
static::$isRegistered = \stream_wrapper_register(static::STREAM_PROTO, __CLASS__);
}
}
}

View File

@@ -1,23 +0,0 @@
<?php
namespace PerformanceBooster\Laravel\SerializableClosure\Support;
class SelfReference
{
/**
* The unique hash representing the object.
*
* @var string
*/
public $hash;
/**
* Creates a new self reference instance.
*
* @param string $hash
* @return void
*/
public function __construct($hash)
{
$this->hash = $hash;
}
}

View File

@@ -1,61 +0,0 @@
<?php
namespace PerformanceBooster\Laravel\SerializableClosure;
use Closure;
class UnsignedSerializableClosure
{
/**
* The closure's serializable.
*
* @var \Laravel\SerializableClosure\Contracts\Serializable
*/
protected $serializable;
/**
* Creates a new serializable closure instance.
*
* @param \Closure $closure
* @return void
*/
public function __construct(Closure $closure)
{
$this->serializable = new Serializers\Native($closure);
}
/**
* Resolve the closure with the given arguments.
*
* @return mixed
*/
public function __invoke()
{
return \call_user_func_array($this->serializable, \func_get_args());
}
/**
* Gets the closure.
*
* @return \Closure
*/
public function getClosure()
{
return $this->serializable->getClosure();
}
/**
* Get the serializable representation of the closure.
*
* @return array{serializable: \Laravel\SerializableClosure\Contracts\Serializable}
*/
public function __serialize()
{
return ['serializable' => $this->serializable];
}
/**
* Restore the closure after serialization.
*
* @param array{serializable: \Laravel\SerializableClosure\Contracts\Serializable} $data
* @return void
*/
public function __unserialize($data)
{
$this->serializable = $data['serializable'];
}
}

View File

@@ -1,126 +0,0 @@
<?hh // decl
namespace FastRoute {
class BadRouteException extends \LogicException {
}
interface RouteParser {
public function parse(string $route): array<array>;
}
class RouteCollector {
public function __construct(RouteParser $routeParser, DataGenerator $dataGenerator);
public function addRoute(mixed $httpMethod, string $route, mixed $handler): void;
public function getData(): array;
}
class Route {
public function __construct(string $httpMethod, mixed $handler, string $regex, array $variables);
public function matches(string $str): bool;
}
interface DataGenerator {
public function addRoute(string $httpMethod, array $routeData, mixed $handler);
public function getData(): array;
}
interface Dispatcher {
const int NOT_FOUND = 0;
const int FOUND = 1;
const int METHOD_NOT_ALLOWED = 2;
public function dispatch(string $httpMethod, string $uri): array;
}
function simpleDispatcher(
(function(RouteCollector): void) $routeDefinitionCallback,
shape(
?'routeParser' => classname<RouteParser>,
?'dataGenerator' => classname<DataGenerator>,
?'dispatcher' => classname<Dispatcher>,
?'routeCollector' => classname<RouteCollector>,
) $options = shape()): Dispatcher;
function cachedDispatcher(
(function(RouteCollector): void) $routeDefinitionCallback,
shape(
?'routeParser' => classname<RouteParser>,
?'dataGenerator' => classname<DataGenerator>,
?'dispatcher' => classname<Dispatcher>,
?'routeCollector' => classname<RouteCollector>,
?'cacheDisabled' => bool,
?'cacheFile' => string,
) $options = shape()): Dispatcher;
}
namespace FastRoute\DataGenerator {
abstract class RegexBasedAbstract implements \FastRoute\DataGenerator {
protected abstract function getApproxChunkSize();
protected abstract function processChunk($regexToRoutesMap);
public function addRoute(string $httpMethod, array $routeData, mixed $handler): void;
public function getData(): array;
}
class CharCountBased extends RegexBasedAbstract {
protected function getApproxChunkSize(): int;
protected function processChunk(array<string, string> $regexToRoutesMap): array<string, mixed>;
}
class GroupCountBased extends RegexBasedAbstract {
protected function getApproxChunkSize(): int;
protected function processChunk(array<string, string> $regexToRoutesMap): array<string, mixed>;
}
class GroupPosBased extends RegexBasedAbstract {
protected function getApproxChunkSize(): int;
protected function processChunk(array<string, string> $regexToRoutesMap): array<string, mixed>;
}
class MarkBased extends RegexBasedAbstract {
protected function getApproxChunkSize(): int;
protected function processChunk(array<string, string> $regexToRoutesMap): array<string, mixed>;
}
}
namespace FastRoute\Dispatcher {
abstract class RegexBasedAbstract implements \FastRoute\Dispatcher {
protected abstract function dispatchVariableRoute(array<array> $routeData, string $uri): array;
public function dispatch(string $httpMethod, string $uri): array;
}
class GroupPosBased extends RegexBasedAbstract {
public function __construct(array $data);
protected function dispatchVariableRoute(array<array> $routeData, string $uri): array;
}
class GroupCountBased extends RegexBasedAbstract {
public function __construct(array $data);
protected function dispatchVariableRoute(array<array> $routeData, string $uri): array;
}
class CharCountBased extends RegexBasedAbstract {
public function __construct(array $data);
protected function dispatchVariableRoute(array<array> $routeData, string $uri): array;
}
class MarkBased extends RegexBasedAbstract {
public function __construct(array $data);
protected function dispatchVariableRoute(array<array> $routeData, string $uri): array;
}
}
namespace FastRoute\RouteParser {
class Std implements \FastRoute\RouteParser {
const string VARIABLE_REGEX = <<<'REGEX'
\{
\s* ([a-zA-Z][a-zA-Z0-9_]*) \s*
(?:
: \s* ([^{}]*(?:\{(?-1)\}[^{}]*)*)
)?
\}
REGEX;
const string DEFAULT_DISPATCH_REGEX = '[^/]+';
public function parse(string $route): array<array>;
}
}

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
syntaxCheck="false"
bootstrap="test/bootstrap.php"
>
<testsuites>
<testsuite name="FastRoute Tests">
<directory>./test/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src/</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -1,28 +0,0 @@
<?xml version="1.0"?>
<psalm
name="Example Psalm config with recommended defaults"
stopOnFirstError="false"
useDocblockTypes="true"
totallyTyped="false"
requireVoidReturnType="false"
>
<projectFiles>
<directory name="src" />
</projectFiles>
<issueHandlers>
<LessSpecificReturnType errorLevel="info" />
<!-- level 3 issues - slightly lazy code writing, but provably low false-negatives -->
<DeprecatedMethod errorLevel="info" />
<MissingClosureReturnType errorLevel="info" />
<MissingReturnType errorLevel="info" />
<MissingPropertyType errorLevel="info" />
<InvalidDocblock errorLevel="info" />
<MisplacedRequiredParam errorLevel="info" />
<PropertyNotSetInConstructor errorLevel="info" />
<MissingConstructor errorLevel="info" />
</issueHandlers>
</psalm>

View File

@@ -1,7 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute;
class BadRouteException extends \LogicException
{
}

View File

@@ -1,25 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute;
interface DataGenerator
{
/**
* Adds a route to the data generator. The route data uses the
* same format that is returned by RouterParser::parser().
*
* The handler doesn't necessarily need to be a callable, it
* can be arbitrary data that will be returned when the route
* matches.
*
* @param string $httpMethod
* @param array $routeData
* @param mixed $handler
*/
public function addRoute($httpMethod, $routeData, $handler);
/**
* Returns dispatcher data in some unspecified format, which
* depends on the used method of dispatch.
*/
public function getData();
}

View File

@@ -1,27 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute\DataGenerator;
class CharCountBased extends RegexBasedAbstract
{
protected function getApproxChunkSize()
{
return 30;
}
protected function processChunk($regexToRoutesMap)
{
$routeMap = [];
$regexes = [];
$suffixLen = 0;
$suffix = '';
$count = \count($regexToRoutesMap);
foreach ($regexToRoutesMap as $regex => $route) {
$suffixLen++;
$suffix .= "\t";
$regexes[] = '(?:' . $regex . '/(\\t{' . $suffixLen . '})\\t{' . ($count - $suffixLen) . '})';
$routeMap[$suffix] = [$route->handler, $route->variables];
}
$regex = '~^(?|' . \implode('|', $regexes) . ')$~';
return ['regex' => $regex, 'suffix' => '/' . $suffix, 'routeMap' => $routeMap];
}
}

View File

@@ -1,26 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute\DataGenerator;
class GroupCountBased extends RegexBasedAbstract
{
protected function getApproxChunkSize()
{
return 10;
}
protected function processChunk($regexToRoutesMap)
{
$routeMap = [];
$regexes = [];
$numGroups = 0;
foreach ($regexToRoutesMap as $regex => $route) {
$numVariables = \count($route->variables);
$numGroups = \max($numGroups, $numVariables);
$regexes[] = $regex . \str_repeat('()', $numGroups - $numVariables);
$routeMap[$numGroups + 1] = [$route->handler, $route->variables];
++$numGroups;
}
$regex = '~^(?|' . \implode('|', $regexes) . ')$~';
return ['regex' => $regex, 'routeMap' => $routeMap];
}
}

View File

@@ -1,24 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute\DataGenerator;
class GroupPosBased extends RegexBasedAbstract
{
protected function getApproxChunkSize()
{
return 10;
}
protected function processChunk($regexToRoutesMap)
{
$routeMap = [];
$regexes = [];
$offset = 1;
foreach ($regexToRoutesMap as $regex => $route) {
$regexes[] = $regex;
$routeMap[$offset] = [$route->handler, $route->variables];
$offset += \count($route->variables);
}
$regex = '~^(?:' . \implode('|', $regexes) . ')$~';
return ['regex' => $regex, 'routeMap' => $routeMap];
}
}

View File

@@ -1,24 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute\DataGenerator;
class MarkBased extends RegexBasedAbstract
{
protected function getApproxChunkSize()
{
return 30;
}
protected function processChunk($regexToRoutesMap)
{
$routeMap = [];
$regexes = [];
$markName = 'a';
foreach ($regexToRoutesMap as $regex => $route) {
$regexes[] = $regex . '(*MARK:' . $markName . ')';
$routeMap[$markName] = [$route->handler, $route->variables];
++$markName;
}
$regex = '~^(?|' . \implode('|', $regexes) . ')$~';
return ['regex' => $regex, 'routeMap' => $routeMap];
}
}

View File

@@ -1,142 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute\DataGenerator;
use PerformanceBooster\FastRoute\BadRouteException;
use PerformanceBooster\FastRoute\DataGenerator;
use PerformanceBooster\FastRoute\Route;
abstract class RegexBasedAbstract implements DataGenerator
{
/** @var mixed[][] */
protected $staticRoutes = [];
/** @var Route[][] */
protected $methodToRegexToRoutesMap = [];
/**
* @return int
*/
protected abstract function getApproxChunkSize();
/**
* @return mixed[]
*/
protected abstract function processChunk($regexToRoutesMap);
public function addRoute($httpMethod, $routeData, $handler)
{
if ($this->isStaticRoute($routeData)) {
$this->addStaticRoute($httpMethod, $routeData, $handler);
} else {
$this->addVariableRoute($httpMethod, $routeData, $handler);
}
}
/**
* @return mixed[]
*/
public function getData()
{
if (empty($this->methodToRegexToRoutesMap)) {
return [$this->staticRoutes, []];
}
return [$this->staticRoutes, $this->generateVariableRouteData()];
}
/**
* @return mixed[]
*/
private function generateVariableRouteData()
{
$data = [];
foreach ($this->methodToRegexToRoutesMap as $method => $regexToRoutesMap) {
$chunkSize = $this->computeChunkSize(\count($regexToRoutesMap));
$chunks = \array_chunk($regexToRoutesMap, $chunkSize, \true);
$data[$method] = \array_map([$this, 'processChunk'], $chunks);
}
return $data;
}
/**
* @param int
* @return int
*/
private function computeChunkSize($count)
{
$numParts = \max(1, \round($count / $this->getApproxChunkSize()));
return (int) \ceil($count / $numParts);
}
/**
* @param mixed[]
* @return bool
*/
private function isStaticRoute($routeData)
{
return \count($routeData) === 1 && \is_string($routeData[0]);
}
private function addStaticRoute($httpMethod, $routeData, $handler)
{
$routeStr = $routeData[0];
if (isset($this->staticRoutes[$httpMethod][$routeStr])) {
throw new BadRouteException(\sprintf('Cannot register two routes matching "%s" for method "%s"', $routeStr, $httpMethod));
}
if (isset($this->methodToRegexToRoutesMap[$httpMethod])) {
foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $route) {
if ($route->matches($routeStr)) {
throw new BadRouteException(\sprintf('Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"', $routeStr, $route->regex, $httpMethod));
}
}
}
$this->staticRoutes[$httpMethod][$routeStr] = $handler;
}
private function addVariableRoute($httpMethod, $routeData, $handler)
{
list($regex, $variables) = $this->buildRegexForRoute($routeData);
if (isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])) {
throw new BadRouteException(\sprintf('Cannot register two routes matching "%s" for method "%s"', $regex, $httpMethod));
}
$this->methodToRegexToRoutesMap[$httpMethod][$regex] = new Route($httpMethod, $handler, $regex, $variables);
}
/**
* @param mixed[]
* @return mixed[]
*/
private function buildRegexForRoute($routeData)
{
$regex = '';
$variables = [];
foreach ($routeData as $part) {
if (\is_string($part)) {
$regex .= \preg_quote($part, '~');
continue;
}
list($varName, $regexPart) = $part;
if (isset($variables[$varName])) {
throw new BadRouteException(\sprintf('Cannot use the same placeholder "%s" twice', $varName));
}
if ($this->regexHasCapturingGroups($regexPart)) {
throw new BadRouteException(\sprintf('Regex "%s" for parameter "%s" contains a capturing group', $regexPart, $varName));
}
$variables[$varName] = $varName;
$regex .= '(' . $regexPart . ')';
}
return [$regex, $variables];
}
/**
* @param string
* @return bool
*/
private function regexHasCapturingGroups($regex)
{
if (\false === \strpos($regex, '(')) {
// Needs to have at least a ( to contain a capturing group
return \false;
}
// Semi-accurate detection for capturing groups
return (bool) \preg_match('~
(?:
\\(\\?\\(
| \\[ [^\\]\\\\]* (?: \\\\ . [^\\]\\\\]* )* \\]
| \\\\ .
) (*SKIP)(*FAIL) |
\\(
(?!
\\? (?! <(?![!=]) | P< | \' )
| \\*
)
~x', $regex);
}
}

View File

@@ -1,25 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute;
interface Dispatcher
{
const NOT_FOUND = 0;
const FOUND = 1;
const METHOD_NOT_ALLOWED = 2;
/**
* Dispatches against the provided HTTP method verb and URI.
*
* Returns array with one of the following formats:
*
* [self::NOT_FOUND]
* [self::METHOD_NOT_ALLOWED, ['GET', 'OTHER_ALLOWED_METHODS']]
* [self::FOUND, $handler, ['varName' => 'value', ...]]
*
* @param string $httpMethod
* @param string $uri
*
* @return array
*/
public function dispatch($httpMethod, $uri);
}

View File

@@ -1,27 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute\Dispatcher;
class CharCountBased extends RegexBasedAbstract
{
public function __construct($data)
{
list($this->staticRouteMap, $this->variableRouteData) = $data;
}
protected function dispatchVariableRoute($routeData, $uri)
{
foreach ($routeData as $data) {
if (!\preg_match($data['regex'], $uri . $data['suffix'], $matches)) {
continue;
}
list($handler, $varNames) = $data['routeMap'][\end($matches)];
$vars = [];
$i = 0;
foreach ($varNames as $varName) {
$vars[$varName] = $matches[++$i];
}
return [self::FOUND, $handler, $vars];
}
return [self::NOT_FOUND];
}
}

View File

@@ -1,27 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute\Dispatcher;
class GroupCountBased extends RegexBasedAbstract
{
public function __construct($data)
{
list($this->staticRouteMap, $this->variableRouteData) = $data;
}
protected function dispatchVariableRoute($routeData, $uri)
{
foreach ($routeData as $data) {
if (!\preg_match($data['regex'], $uri, $matches)) {
continue;
}
list($handler, $varNames) = $data['routeMap'][\count($matches)];
$vars = [];
$i = 0;
foreach ($varNames as $varName) {
$vars[$varName] = $matches[++$i];
}
return [self::FOUND, $handler, $vars];
}
return [self::NOT_FOUND];
}
}

View File

@@ -1,29 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute\Dispatcher;
class GroupPosBased extends RegexBasedAbstract
{
public function __construct($data)
{
list($this->staticRouteMap, $this->variableRouteData) = $data;
}
protected function dispatchVariableRoute($routeData, $uri)
{
foreach ($routeData as $data) {
if (!\preg_match($data['regex'], $uri, $matches)) {
continue;
}
// find first non-empty match
for ($i = 1; '' === $matches[$i]; ++$i) {
}
list($handler, $varNames) = $data['routeMap'][$i];
$vars = [];
foreach ($varNames as $varName) {
$vars[$varName] = $matches[$i++];
}
return [self::FOUND, $handler, $vars];
}
return [self::NOT_FOUND];
}
}

View File

@@ -1,27 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute\Dispatcher;
class MarkBased extends RegexBasedAbstract
{
public function __construct($data)
{
list($this->staticRouteMap, $this->variableRouteData) = $data;
}
protected function dispatchVariableRoute($routeData, $uri)
{
foreach ($routeData as $data) {
if (!\preg_match($data['regex'], $uri, $matches)) {
continue;
}
list($handler, $varNames) = $data['routeMap'][$matches['MARK']];
$vars = [];
$i = 0;
foreach ($varNames as $varName) {
$vars[$varName] = $matches[++$i];
}
return [self::FOUND, $handler, $vars];
}
return [self::NOT_FOUND];
}
}

View File

@@ -1,75 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute\Dispatcher;
use PerformanceBooster\FastRoute\Dispatcher;
abstract class RegexBasedAbstract implements Dispatcher
{
/** @var mixed[][] */
protected $staticRouteMap = [];
/** @var mixed[] */
protected $variableRouteData = [];
/**
* @return mixed[]
*/
protected abstract function dispatchVariableRoute($routeData, $uri);
public function dispatch($httpMethod, $uri)
{
if (isset($this->staticRouteMap[$httpMethod][$uri])) {
$handler = $this->staticRouteMap[$httpMethod][$uri];
return [self::FOUND, $handler, []];
}
$varRouteData = $this->variableRouteData;
if (isset($varRouteData[$httpMethod])) {
$result = $this->dispatchVariableRoute($varRouteData[$httpMethod], $uri);
if ($result[0] === self::FOUND) {
return $result;
}
}
// For HEAD requests, attempt fallback to GET
if ($httpMethod === 'HEAD') {
if (isset($this->staticRouteMap['GET'][$uri])) {
$handler = $this->staticRouteMap['GET'][$uri];
return [self::FOUND, $handler, []];
}
if (isset($varRouteData['GET'])) {
$result = $this->dispatchVariableRoute($varRouteData['GET'], $uri);
if ($result[0] === self::FOUND) {
return $result;
}
}
}
// If nothing else matches, try fallback routes
if (isset($this->staticRouteMap['*'][$uri])) {
$handler = $this->staticRouteMap['*'][$uri];
return [self::FOUND, $handler, []];
}
if (isset($varRouteData['*'])) {
$result = $this->dispatchVariableRoute($varRouteData['*'], $uri);
if ($result[0] === self::FOUND) {
return $result;
}
}
// Find allowed methods for this URI by matching against all other HTTP methods as well
$allowedMethods = [];
foreach ($this->staticRouteMap as $method => $uriMap) {
if ($method !== $httpMethod && isset($uriMap[$uri])) {
$allowedMethods[] = $method;
}
}
foreach ($varRouteData as $method => $routeData) {
if ($method === $httpMethod) {
continue;
}
$result = $this->dispatchVariableRoute($routeData, $uri);
if ($result[0] === self::FOUND) {
$allowedMethods[] = $method;
}
}
// If there are no allowed methods the route simply does not exist
if ($allowedMethods) {
return [self::METHOD_NOT_ALLOWED, $allowedMethods];
}
return [self::NOT_FOUND];
}
}

View File

@@ -1,42 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute;
class Route
{
/** @var string */
public $httpMethod;
/** @var string */
public $regex;
/** @var array */
public $variables;
/** @var mixed */
public $handler;
/**
* Constructs a route (value object).
*
* @param string $httpMethod
* @param mixed $handler
* @param string $regex
* @param array $variables
*/
public function __construct($httpMethod, $handler, $regex, $variables)
{
$this->httpMethod = $httpMethod;
$this->handler = $handler;
$this->regex = $regex;
$this->variables = $variables;
}
/**
* Tests whether this route matches the given string.
*
* @param string $str
*
* @return bool
*/
public function matches($str)
{
$regex = '~^' . $this->regex . '$~';
return (bool) \preg_match($regex, $str);
}
}

View File

@@ -1,140 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute;
class RouteCollector
{
/** @var RouteParser */
protected $routeParser;
/** @var DataGenerator */
protected $dataGenerator;
/** @var string */
protected $currentGroupPrefix;
/**
* Constructs a route collector.
*
* @param RouteParser $routeParser
* @param DataGenerator $dataGenerator
*/
public function __construct(RouteParser $routeParser, DataGenerator $dataGenerator)
{
$this->routeParser = $routeParser;
$this->dataGenerator = $dataGenerator;
$this->currentGroupPrefix = '';
}
/**
* Adds a route to the collection.
*
* The syntax used in the $route string depends on the used route parser.
*
* @param string|string[] $httpMethod
* @param string $route
* @param mixed $handler
*/
public function addRoute($httpMethod, $route, $handler)
{
$route = $this->currentGroupPrefix . $route;
$routeDatas = $this->routeParser->parse($route);
foreach ((array) $httpMethod as $method) {
foreach ($routeDatas as $routeData) {
$this->dataGenerator->addRoute($method, $routeData, $handler);
}
}
}
/**
* Create a route group with a common prefix.
*
* All routes created in the passed callback will have the given group prefix prepended.
*
* @param string $prefix
* @param callable $callback
*/
public function addGroup($prefix, callable $callback)
{
$previousGroupPrefix = $this->currentGroupPrefix;
$this->currentGroupPrefix = $previousGroupPrefix . $prefix;
$callback($this);
$this->currentGroupPrefix = $previousGroupPrefix;
}
/**
* Adds a GET route to the collection
*
* This is simply an alias of $this->addRoute('GET', $route, $handler)
*
* @param string $route
* @param mixed $handler
*/
public function get($route, $handler)
{
$this->addRoute('GET', $route, $handler);
}
/**
* Adds a POST route to the collection
*
* This is simply an alias of $this->addRoute('POST', $route, $handler)
*
* @param string $route
* @param mixed $handler
*/
public function post($route, $handler)
{
$this->addRoute('POST', $route, $handler);
}
/**
* Adds a PUT route to the collection
*
* This is simply an alias of $this->addRoute('PUT', $route, $handler)
*
* @param string $route
* @param mixed $handler
*/
public function put($route, $handler)
{
$this->addRoute('PUT', $route, $handler);
}
/**
* Adds a DELETE route to the collection
*
* This is simply an alias of $this->addRoute('DELETE', $route, $handler)
*
* @param string $route
* @param mixed $handler
*/
public function delete($route, $handler)
{
$this->addRoute('DELETE', $route, $handler);
}
/**
* Adds a PATCH route to the collection
*
* This is simply an alias of $this->addRoute('PATCH', $route, $handler)
*
* @param string $route
* @param mixed $handler
*/
public function patch($route, $handler)
{
$this->addRoute('PATCH', $route, $handler);
}
/**
* Adds a HEAD route to the collection
*
* This is simply an alias of $this->addRoute('HEAD', $route, $handler)
*
* @param string $route
* @param mixed $handler
*/
public function head($route, $handler)
{
$this->addRoute('HEAD', $route, $handler);
}
/**
* Returns the collected route data, as provided by the data generator.
*
* @return array
*/
public function getData()
{
return $this->dataGenerator->getData();
}
}

View File

@@ -1,37 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute;
interface RouteParser
{
/**
* Parses a route string into multiple route data arrays.
*
* The expected output is defined using an example:
*
* For the route string "/fixedRoutePart/{varName}[/moreFixed/{varName2:\d+}]", if {varName} is interpreted as
* a placeholder and [...] is interpreted as an optional route part, the expected result is:
*
* [
* // first route: without optional part
* [
* "/fixedRoutePart/",
* ["varName", "[^/]+"],
* ],
* // second route: with optional part
* [
* "/fixedRoutePart/",
* ["varName", "[^/]+"],
* "/moreFixed/",
* ["varName2", [0-9]+"],
* ],
* ]
*
* Here one route string was converted into two route data arrays.
*
* @param string $route Route string to parse
*
* @return mixed[][] Array of route data arrays
*/
public function parse($route);
}

View File

@@ -1,72 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute\RouteParser;
use PerformanceBooster\FastRoute\BadRouteException;
use PerformanceBooster\FastRoute\RouteParser;
/**
* Parses route strings of the following form:
*
* "/user/{name}[/{id:[0-9]+}]"
*/
class Std implements RouteParser
{
const VARIABLE_REGEX = <<<'REGEX'
\{
\s* ([a-zA-Z_][a-zA-Z0-9_-]*) \s*
(?:
: \s* ([^{}]*(?:\{(?-1)\}[^{}]*)*)
)?
\}
REGEX;
const DEFAULT_DISPATCH_REGEX = '[^/]+';
public function parse($route)
{
$routeWithoutClosingOptionals = \rtrim($route, ']');
$numOptionals = \strlen($route) - \strlen($routeWithoutClosingOptionals);
// Split on [ while skipping placeholders
$segments = \preg_split('~' . self::VARIABLE_REGEX . '(*SKIP)(*F) | \\[~x', $routeWithoutClosingOptionals);
if ($numOptionals !== \count($segments) - 1) {
// If there are any ] in the middle of the route, throw a more specific error message
if (\preg_match('~' . self::VARIABLE_REGEX . '(*SKIP)(*F) | \\]~x', $routeWithoutClosingOptionals)) {
throw new BadRouteException('Optional segments can only occur at the end of a route');
}
throw new BadRouteException("Number of opening '[' and closing ']' does not match");
}
$currentRoute = '';
$routeDatas = [];
foreach ($segments as $n => $segment) {
if ($segment === '' && $n !== 0) {
throw new BadRouteException('Empty optional part');
}
$currentRoute .= $segment;
$routeDatas[] = $this->parsePlaceholders($currentRoute);
}
return $routeDatas;
}
/**
* Parses a route string that does not contain optional segments.
*
* @param string
* @return mixed[]
*/
private function parsePlaceholders($route)
{
if (!\preg_match_all('~' . self::VARIABLE_REGEX . '~x', $route, $matches, \PREG_OFFSET_CAPTURE | \PREG_SET_ORDER)) {
return [$route];
}
$offset = 0;
$routeData = [];
foreach ($matches as $set) {
if ($set[0][1] > $offset) {
$routeData[] = \substr($route, $offset, $set[0][1] - $offset);
}
$routeData[] = [$set[1][0], isset($set[2]) ? \trim($set[2][0]) : self::DEFAULT_DISPATCH_REGEX];
$offset = $set[0][1] + \strlen($set[0][0]);
}
if ($offset !== \strlen($route)) {
$routeData[] = \substr($route, $offset);
}
return $routeData;
}
}

View File

@@ -1,11 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute;
require __DIR__ . '/functions.php';
\spl_autoload_register(function ($class) {
if (\strpos($class, 'FastRoute\\') === 0) {
$name = \substr($class, \strlen('FastRoute'));
require __DIR__ . \strtr($name, '\\', \DIRECTORY_SEPARATOR) . '.php';
}
});

View File

@@ -1,48 +0,0 @@
<?php
namespace PerformanceBooster\FastRoute;
if (!\function_exists('PerformanceBooster\\FastRoute\\simpleDispatcher')) {
/**
* @param callable $routeDefinitionCallback
* @param array $options
*
* @return Dispatcher
*/
function simpleDispatcher(callable $routeDefinitionCallback, array $options = [])
{
$options += ['routeParser' => 'PerformanceBooster\\FastRoute\\RouteParser\\Std', 'dataGenerator' => 'PerformanceBooster\\FastRoute\\DataGenerator\\GroupCountBased', 'dispatcher' => 'PerformanceBooster\\FastRoute\\Dispatcher\\GroupCountBased', 'routeCollector' => 'PerformanceBooster\\FastRoute\\RouteCollector'];
/** @var RouteCollector $routeCollector */
$routeCollector = new $options['routeCollector'](new $options['routeParser'](), new $options['dataGenerator']());
$routeDefinitionCallback($routeCollector);
return new $options['dispatcher']($routeCollector->getData());
}
/**
* @param callable $routeDefinitionCallback
* @param array $options
*
* @return Dispatcher
*/
function cachedDispatcher(callable $routeDefinitionCallback, array $options = [])
{
$options += ['routeParser' => 'PerformanceBooster\\FastRoute\\RouteParser\\Std', 'dataGenerator' => 'PerformanceBooster\\FastRoute\\DataGenerator\\GroupCountBased', 'dispatcher' => 'PerformanceBooster\\FastRoute\\Dispatcher\\GroupCountBased', 'routeCollector' => 'PerformanceBooster\\FastRoute\\RouteCollector', 'cacheDisabled' => \false];
if (!isset($options['cacheFile'])) {
throw new \LogicException('Must specify "cacheFile" option');
}
if (!$options['cacheDisabled'] && \file_exists($options['cacheFile'])) {
$dispatchData = (require $options['cacheFile']);
if (!\is_array($dispatchData)) {
throw new \RuntimeException('Invalid cache file "' . $options['cacheFile'] . '"');
}
return new $options['dispatcher']($dispatchData);
}
$routeCollector = new $options['routeCollector'](new $options['routeParser'](), new $options['dataGenerator']());
$routeDefinitionCallback($routeCollector);
/** @var RouteCollector $routeCollector */
$dispatchData = $routeCollector->getData();
if (!$options['cacheDisabled']) {
\file_put_contents($options['cacheFile'], '<?php return ' . \var_export($dispatchData, \true) . ';');
}
return new $options['dispatcher']($dispatchData);
}
}

View File

@@ -1,235 +0,0 @@
<?php
declare (strict_types=1);
namespace Nyholm\Psr7Server;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*/
final class ServerRequestCreator implements \Nyholm\Psr7Server\ServerRequestCreatorInterface
{
private $serverRequestFactory;
private $uriFactory;
private $uploadedFileFactory;
private $streamFactory;
public function __construct(ServerRequestFactoryInterface $serverRequestFactory, UriFactoryInterface $uriFactory, UploadedFileFactoryInterface $uploadedFileFactory, StreamFactoryInterface $streamFactory)
{
$this->serverRequestFactory = $serverRequestFactory;
$this->uriFactory = $uriFactory;
$this->uploadedFileFactory = $uploadedFileFactory;
$this->streamFactory = $streamFactory;
}
/**
* {@inheritdoc}
*/
public function fromGlobals() : ServerRequestInterface
{
$server = $_SERVER;
if (\false === isset($server['REQUEST_METHOD'])) {
$server['REQUEST_METHOD'] = 'GET';
}
$headers = \function_exists('getallheaders') ? \getallheaders() : static::getHeadersFromServer($_SERVER);
$post = null;
if ('POST' === $this->getMethodFromEnv($server)) {
foreach ($headers as $headerName => $headerValue) {
if (\true === \is_int($headerName) || 'content-type' !== \strtolower($headerName)) {
continue;
}
if (\in_array(\strtolower(\trim(\explode(';', $headerValue, 2)[0])), ['application/x-www-form-urlencoded', 'multipart/form-data'])) {
$post = $_POST;
break;
}
}
}
return $this->fromArrays($server, $headers, $_COOKIE, $_GET, $post, $_FILES, \fopen('php://input', 'r') ?: null);
}
/**
* {@inheritdoc}
*/
public function fromArrays(array $server, array $headers = [], array $cookie = [], array $get = [], ?array $post = null, array $files = [], $body = null) : ServerRequestInterface
{
$method = $this->getMethodFromEnv($server);
$uri = $this->getUriFromEnvWithHTTP($server);
$protocol = isset($server['SERVER_PROTOCOL']) ? \str_replace('HTTP/', '', $server['SERVER_PROTOCOL']) : '1.1';
$serverRequest = $this->serverRequestFactory->createServerRequest($method, $uri, $server);
foreach ($headers as $name => $value) {
// Because PHP automatically casts array keys set with numeric strings to integers, we have to make sure
// that numeric headers will not be sent along as integers, as withAddedHeader can only accept strings.
if (\is_int($name)) {
$name = (string) $name;
}
$serverRequest = $serverRequest->withAddedHeader($name, $value);
}
$serverRequest = $serverRequest->withProtocolVersion($protocol)->withCookieParams($cookie)->withQueryParams($get)->withParsedBody($post)->withUploadedFiles($this->normalizeFiles($files));
if (null === $body) {
return $serverRequest;
}
if (\is_resource($body)) {
$body = $this->streamFactory->createStreamFromResource($body);
} elseif (\is_string($body)) {
$body = $this->streamFactory->createStream($body);
} elseif (!$body instanceof StreamInterface) {
throw new \InvalidArgumentException('The $body parameter to ServerRequestCreator::fromArrays must be string, resource or StreamInterface');
}
return $serverRequest->withBody($body);
}
/**
* Implementation from Laminas\Diactoros\marshalHeadersFromSapi().
*/
public static function getHeadersFromServer(array $server) : array
{
$headers = [];
foreach ($server as $key => $value) {
// Apache prefixes environment variables with REDIRECT_
// if they are added by rewrite rules
if (0 === \strpos($key, 'REDIRECT_')) {
$key = \substr($key, 9);
// We will not overwrite existing variables with the
// prefixed versions, though
if (\array_key_exists($key, $server)) {
continue;
}
}
if ($value && 0 === \strpos($key, 'HTTP_')) {
$name = \strtr(\strtolower(\substr($key, 5)), '_', '-');
$headers[$name] = $value;
continue;
}
if ($value && 0 === \strpos($key, 'CONTENT_')) {
$name = 'content-' . \strtolower(\substr($key, 8));
$headers[$name] = $value;
continue;
}
}
return $headers;
}
private function getMethodFromEnv(array $environment) : string
{
if (\false === isset($environment['REQUEST_METHOD'])) {
throw new \InvalidArgumentException('Cannot determine HTTP method');
}
return $environment['REQUEST_METHOD'];
}
private function getUriFromEnvWithHTTP(array $environment) : UriInterface
{
$uri = $this->createUriFromArray($environment);
if (empty($uri->getScheme())) {
$uri = $uri->withScheme('http');
}
return $uri;
}
/**
* Return an UploadedFile instance array.
*
* @param array $files A array which respect $_FILES structure
*
* @return UploadedFileInterface[]
*
* @throws \InvalidArgumentException for unrecognized values
*/
private function normalizeFiles(array $files) : array
{
$normalized = [];
foreach ($files as $key => $value) {
if ($value instanceof UploadedFileInterface) {
$normalized[$key] = $value;
} elseif (\is_array($value) && isset($value['tmp_name'])) {
$normalized[$key] = $this->createUploadedFileFromSpec($value);
} elseif (\is_array($value)) {
$normalized[$key] = $this->normalizeFiles($value);
} else {
throw new \InvalidArgumentException('Invalid value in files specification');
}
}
return $normalized;
}
/**
* Create and return an UploadedFile instance from a $_FILES specification.
*
* If the specification represents an array of values, this method will
* delegate to normalizeNestedFileSpec() and return that return value.
*
* @param array $value $_FILES struct
*
* @return array|UploadedFileInterface
*/
private function createUploadedFileFromSpec(array $value)
{
if (\is_array($value['tmp_name'])) {
return $this->normalizeNestedFileSpec($value);
}
if (\UPLOAD_ERR_OK !== $value['error']) {
$stream = $this->streamFactory->createStream();
} else {
try {
$stream = $this->streamFactory->createStreamFromFile($value['tmp_name']);
} catch (\RuntimeException $e) {
$stream = $this->streamFactory->createStream();
}
}
return $this->uploadedFileFactory->createUploadedFile($stream, (int) $value['size'], (int) $value['error'], $value['name'], $value['type']);
}
/**
* Normalize an array of file specifications.
*
* Loops through all nested files and returns a normalized array of
* UploadedFileInterface instances.
*
* @return UploadedFileInterface[]
*/
private function normalizeNestedFileSpec(array $files = []) : array
{
$normalizedFiles = [];
foreach (\array_keys($files['tmp_name']) as $key) {
$spec = ['tmp_name' => $files['tmp_name'][$key], 'size' => $files['size'][$key], 'error' => $files['error'][$key], 'name' => $files['name'][$key], 'type' => $files['type'][$key]];
$normalizedFiles[$key] = $this->createUploadedFileFromSpec($spec);
}
return $normalizedFiles;
}
/**
* Create a new uri from server variable.
*
* @param array $server typically $_SERVER or similar structure
*/
private function createUriFromArray(array $server) : UriInterface
{
$uri = $this->uriFactory->createUri('');
if (isset($server['HTTP_X_FORWARDED_PROTO'])) {
$uri = $uri->withScheme($server['HTTP_X_FORWARDED_PROTO']);
} else {
if (isset($server['REQUEST_SCHEME'])) {
$uri = $uri->withScheme($server['REQUEST_SCHEME']);
} elseif (isset($server['HTTPS'])) {
$uri = $uri->withScheme('on' === $server['HTTPS'] ? 'https' : 'http');
}
if (isset($server['SERVER_PORT'])) {
$uri = $uri->withPort($server['SERVER_PORT']);
}
}
if (isset($server['HTTP_HOST'])) {
if (1 === \preg_match('/^(.+)\\:(\\d+)$/', $server['HTTP_HOST'], $matches)) {
$uri = $uri->withHost($matches[1])->withPort($matches[2]);
} else {
$uri = $uri->withHost($server['HTTP_HOST']);
}
} elseif (isset($server['SERVER_NAME'])) {
$uri = $uri->withHost($server['SERVER_NAME']);
}
if (isset($server['REQUEST_URI'])) {
$uri = $uri->withPath(\current(\explode('?', $server['REQUEST_URI'])));
}
if (isset($server['QUERY_STRING'])) {
$uri = $uri->withQuery($server['QUERY_STRING']);
}
return $uri;
}
}

View File

@@ -1,44 +0,0 @@
<?php
declare (strict_types=1);
namespace Nyholm\Psr7Server;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*/
interface ServerRequestCreatorInterface
{
/**
* Create a new server request from the current environment variables.
* Defaults to a GET request to minimise the risk of an \InvalidArgumentException.
* Includes the current request headers as supplied by the server through `getallheaders()`.
* If `getallheaders()` is unavailable on the current server it will fallback to its own `getHeadersFromServer()` method.
* Defaults to php://input for the request body.
*
* @throws \InvalidArgumentException if no valid method or URI can be determined
*/
public function fromGlobals() : ServerRequestInterface;
/**
* Create a new server request from a set of arrays.
*
* @param array $server typically $_SERVER or similar structure
* @param array $headers typically the output of getallheaders() or similar structure
* @param array $cookie typically $_COOKIE or similar structure
* @param array $get typically $_GET or similar structure
* @param array|null $post typically $_POST or similar structure, represents parsed request body
* @param array $files typically $_FILES or similar structure
* @param StreamInterface|resource|string|null $body Typically stdIn
*
* @throws \InvalidArgumentException if no valid method or URI can be determined
*/
public function fromArrays(array $server, array $headers = [], array $cookie = [], array $get = [], ?array $post = null, array $files = [], $body = null) : ServerRequestInterface;
/**
* Get parsed headers from ($_SERVER) array.
*
* @param array $server typically $_SERVER or similar structure
*/
public static function getHeadersFromServer(array $server) : array;
}

View File

@@ -1,50 +0,0 @@
<?php
declare (strict_types=1);
namespace Nyholm\Psr7\Factory;
use PerformanceBooster\Http\Message\MessageFactory;
use PerformanceBooster\Http\Message\StreamFactory;
use PerformanceBooster\Http\Message\UriFactory;
use Nyholm\Psr7\Request;
use Nyholm\Psr7\Response;
use Nyholm\Psr7\Stream;
use Nyholm\Psr7\Uri;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
if (!\interface_exists(MessageFactory::class)) {
throw new \LogicException('You cannot use "Nyholm\\Psr7\\Factory\\HttplugFactory" as the "php-http/message-factory" package is not installed. Try running "composer require php-http/message-factory". Note that this package is deprecated, use "psr/http-factory" instead');
}
@\trigger_error('Class "Nyholm\\Psr7\\Factory\\HttplugFactory" is deprecated since version 1.8, use "Nyholm\\Psr7\\Factory\\Psr17Factory" instead.', \E_USER_DEPRECATED);
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*
* @deprecated since version 1.8, use Psr17Factory instead
*/
class HttplugFactory implements MessageFactory, StreamFactory, UriFactory
{
public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1') : RequestInterface
{
return new Request($method, $uri, $headers, $body, $protocolVersion);
}
public function createResponse($statusCode = 200, $reasonPhrase = null, array $headers = [], $body = null, $version = '1.1') : ResponseInterface
{
return new Response((int) $statusCode, $headers, $body, $version, $reasonPhrase);
}
public function createStream($body = null) : StreamInterface
{
return Stream::create($body ?? '');
}
public function createUri($uri = '') : UriInterface
{
if ($uri instanceof UriInterface) {
return $uri;
}
return new Uri($uri);
}
}

View File

@@ -1,80 +0,0 @@
<?php
declare (strict_types=1);
namespace Nyholm\Psr7\Factory;
use Nyholm\Psr7\Request;
use Nyholm\Psr7\Response;
use Nyholm\Psr7\ServerRequest;
use Nyholm\Psr7\Stream;
use Nyholm\Psr7\UploadedFile;
use Nyholm\Psr7\Uri;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*/
class Psr17Factory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface
{
public function createRequest(string $method, $uri) : RequestInterface
{
return new Request($method, $uri);
}
public function createResponse(int $code = 200, string $reasonPhrase = '') : ResponseInterface
{
if (2 > \func_num_args()) {
// This will make the Response class to use a custom reasonPhrase
$reasonPhrase = null;
}
return new Response($code, [], null, '1.1', $reasonPhrase);
}
public function createStream(string $content = '') : StreamInterface
{
return Stream::create($content);
}
public function createStreamFromFile(string $filename, string $mode = 'r') : StreamInterface
{
if ('' === $filename) {
throw new \RuntimeException('Path cannot be empty');
}
if (\false === ($resource = @\fopen($filename, $mode))) {
if ('' === $mode || \false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], \true)) {
throw new \InvalidArgumentException(\sprintf('The mode "%s" is invalid.', $mode));
}
throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $filename, \error_get_last()['message'] ?? ''));
}
return Stream::create($resource);
}
public function createStreamFromResource($resource) : StreamInterface
{
return Stream::create($resource);
}
public function createUploadedFile(StreamInterface $stream, ?int $size = null, int $error = \UPLOAD_ERR_OK, ?string $clientFilename = null, ?string $clientMediaType = null) : UploadedFileInterface
{
if (null === $size) {
$size = $stream->getSize();
}
return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType);
}
public function createUri(string $uri = '') : UriInterface
{
return new Uri($uri);
}
public function createServerRequest(string $method, $uri, array $serverParams = []) : ServerRequestInterface
{
return new ServerRequest($method, $uri, [], null, '1.1', $serverParams);
}
}

View File

@@ -1,195 +0,0 @@
<?php
declare (strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\StreamInterface;
/**
* Trait implementing functionality common to requests and responses.
*
* @author Michael Dowling and contributors to guzzlehttp/psr7
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise
*/
trait MessageTrait
{
/** @var array Map of all registered headers, as original name => array of values */
private $headers = [];
/** @var array Map of lowercase header name => original name at registration */
private $headerNames = [];
/** @var string */
private $protocol = '1.1';
/** @var StreamInterface|null */
private $stream;
public function getProtocolVersion() : string
{
return $this->protocol;
}
/**
* @return static
*/
public function withProtocolVersion($version) : MessageInterface
{
if (!\is_scalar($version)) {
throw new \InvalidArgumentException('Protocol version must be a string');
}
if ($this->protocol === $version) {
return $this;
}
$new = clone $this;
$new->protocol = (string) $version;
return $new;
}
public function getHeaders() : array
{
return $this->headers;
}
public function hasHeader($header) : bool
{
return isset($this->headerNames[\strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')]);
}
public function getHeader($header) : array
{
if (!\is_string($header)) {
throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string');
}
$header = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
if (!isset($this->headerNames[$header])) {
return [];
}
$header = $this->headerNames[$header];
return $this->headers[$header];
}
public function getHeaderLine($header) : string
{
return \implode(', ', $this->getHeader($header));
}
/**
* @return static
*/
public function withHeader($header, $value) : MessageInterface
{
$value = $this->validateAndTrimHeader($header, $value);
$normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
$new = clone $this;
if (isset($new->headerNames[$normalized])) {
unset($new->headers[$new->headerNames[$normalized]]);
}
$new->headerNames[$normalized] = $header;
$new->headers[$header] = $value;
return $new;
}
/**
* @return static
*/
public function withAddedHeader($header, $value) : MessageInterface
{
if (!\is_string($header) || '' === $header) {
throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string');
}
$new = clone $this;
$new->setHeaders([$header => $value]);
return $new;
}
/**
* @return static
*/
public function withoutHeader($header) : MessageInterface
{
if (!\is_string($header)) {
throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string');
}
$normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
if (!isset($this->headerNames[$normalized])) {
return $this;
}
$header = $this->headerNames[$normalized];
$new = clone $this;
unset($new->headers[$header], $new->headerNames[$normalized]);
return $new;
}
public function getBody() : StreamInterface
{
if (null === $this->stream) {
$this->stream = \Nyholm\Psr7\Stream::create('');
}
return $this->stream;
}
/**
* @return static
*/
public function withBody(StreamInterface $body) : MessageInterface
{
if ($body === $this->stream) {
return $this;
}
$new = clone $this;
$new->stream = $body;
return $new;
}
private function setHeaders(array $headers) : void
{
foreach ($headers as $header => $value) {
if (\is_int($header)) {
// If a header name was set to a numeric string, PHP will cast the key to an int.
// We must cast it back to a string in order to comply with validation.
$header = (string) $header;
}
$value = $this->validateAndTrimHeader($header, $value);
$normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
if (isset($this->headerNames[$normalized])) {
$header = $this->headerNames[$normalized];
$this->headers[$header] = \array_merge($this->headers[$header], $value);
} else {
$this->headerNames[$normalized] = $header;
$this->headers[$header] = $value;
}
}
}
/**
* Make sure the header complies with RFC 7230.
*
* Header names must be a non-empty string consisting of token characters.
*
* Header values must be strings consisting of visible characters with all optional
* leading and trailing whitespace stripped. This method will always strip such
* optional whitespace. Note that the method does not allow folding whitespace within
* the values as this was deprecated for almost all instances by the RFC.
*
* header-field = field-name ":" OWS field-value OWS
* field-name = 1*( "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^"
* / "_" / "`" / "|" / "~" / %x30-39 / ( %x41-5A / %x61-7A ) )
* OWS = *( SP / HTAB )
* field-value = *( ( %x21-7E / %x80-FF ) [ 1*( SP / HTAB ) ( %x21-7E / %x80-FF ) ] )
*
* @see https://tools.ietf.org/html/rfc7230#section-3.2.4
*/
private function validateAndTrimHeader($header, $values) : array
{
if (!\is_string($header) || 1 !== \preg_match("@^[!#\$%&'*+.^_`|~0-9A-Za-z-]+\$@D", $header)) {
throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string');
}
if (!\is_array($values)) {
// This is simple, just one value.
if (!\is_numeric($values) && !\is_string($values) || 1 !== \preg_match("@^[ \t!-~\x80-\xff]*\$@", (string) $values)) {
throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings');
}
return [\trim((string) $values, " \t")];
}
if (empty($values)) {
throw new \InvalidArgumentException('Header values must be a string or an array of strings, empty array given');
}
// Assert Non empty array
$returnValues = [];
foreach ($values as $v) {
if (!\is_numeric($v) && !\is_string($v) || 1 !== \preg_match("@^[ \t!-~\x80-\xff]*\$@D", (string) $v)) {
throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings');
}
$returnValues[] = \trim((string) $v, " \t");
}
return $returnValues;
}
}

View File

@@ -1,43 +0,0 @@
<?php
declare (strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*/
class Request implements RequestInterface
{
use \Nyholm\Psr7\MessageTrait;
use \Nyholm\Psr7\RequestTrait;
/**
* @param string $method HTTP method
* @param string|UriInterface $uri URI
* @param array $headers Request headers
* @param string|resource|StreamInterface|null $body Request body
* @param string $version Protocol version
*/
public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1')
{
if (!$uri instanceof UriInterface) {
$uri = new \Nyholm\Psr7\Uri($uri);
}
$this->method = $method;
$this->uri = $uri;
$this->setHeaders($headers);
$this->protocol = $version;
if (!$this->hasHeader('Host')) {
$this->updateHostFromUri();
}
// If we got no body, defer initialization of the stream until Request::getBody()
if ('' !== $body && null !== $body) {
$this->stream = \Nyholm\Psr7\Stream::create($body);
}
}
}

View File

@@ -1,103 +0,0 @@
<?php
declare (strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
/**
* @author Michael Dowling and contributors to guzzlehttp/psr7
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise
*/
trait RequestTrait
{
/** @var string */
private $method;
/** @var string|null */
private $requestTarget;
/** @var UriInterface|null */
private $uri;
public function getRequestTarget() : string
{
if (null !== $this->requestTarget) {
return $this->requestTarget;
}
if ('' === ($target = $this->uri->getPath())) {
$target = '/';
}
if ('' !== $this->uri->getQuery()) {
$target .= '?' . $this->uri->getQuery();
}
return $target;
}
/**
* @return static
*/
public function withRequestTarget($requestTarget) : RequestInterface
{
if (!\is_string($requestTarget)) {
throw new \InvalidArgumentException('Request target must be a string');
}
if (\preg_match('#\\s#', $requestTarget)) {
throw new \InvalidArgumentException('Invalid request target provided; cannot contain whitespace');
}
$new = clone $this;
$new->requestTarget = $requestTarget;
return $new;
}
public function getMethod() : string
{
return $this->method;
}
/**
* @return static
*/
public function withMethod($method) : RequestInterface
{
if (!\is_string($method)) {
throw new \InvalidArgumentException('Method must be a string');
}
$new = clone $this;
$new->method = $method;
return $new;
}
public function getUri() : UriInterface
{
return $this->uri;
}
/**
* @return static
*/
public function withUri(UriInterface $uri, $preserveHost = \false) : RequestInterface
{
if ($uri === $this->uri) {
return $this;
}
$new = clone $this;
$new->uri = $uri;
if (!$preserveHost || !$this->hasHeader('Host')) {
$new->updateHostFromUri();
}
return $new;
}
private function updateHostFromUri() : void
{
if ('' === ($host = $this->uri->getHost())) {
return;
}
if (null !== ($port = $this->uri->getPort())) {
$host .= ':' . $port;
}
if (isset($this->headerNames['host'])) {
$header = $this->headerNames['host'];
} else {
$this->headerNames['host'] = $header = 'Host';
}
// Ensure Host is the first header.
// See: http://tools.ietf.org/html/rfc7230#section-5.4
$this->headers = [$header => [$host]] + $this->headers;
}
}

View File

@@ -1,74 +0,0 @@
<?php
declare (strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
/**
* @author Michael Dowling and contributors to guzzlehttp/psr7
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*/
class Response implements ResponseInterface
{
use \Nyholm\Psr7\MessageTrait;
/** @var array Map of standard HTTP status code/reason phrases */
private const PHRASES = [100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 511 => 'Network Authentication Required'];
/** @var string */
private $reasonPhrase = '';
/** @var int */
private $statusCode;
/**
* @param int $status Status code
* @param array $headers Response headers
* @param string|resource|StreamInterface|null $body Response body
* @param string $version Protocol version
* @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
*/
public function __construct(int $status = 200, array $headers = [], $body = null, string $version = '1.1', ?string $reason = null)
{
// If we got no body, defer initialization of the stream until Response::getBody()
if ('' !== $body && null !== $body) {
$this->stream = \Nyholm\Psr7\Stream::create($body);
}
$this->statusCode = $status;
$this->setHeaders($headers);
if (null === $reason && isset(self::PHRASES[$this->statusCode])) {
$this->reasonPhrase = self::PHRASES[$status];
} else {
$this->reasonPhrase = $reason ?? '';
}
$this->protocol = $version;
}
public function getStatusCode() : int
{
return $this->statusCode;
}
public function getReasonPhrase() : string
{
return $this->reasonPhrase;
}
/**
* @return static
*/
public function withStatus($code, $reasonPhrase = '') : ResponseInterface
{
if (!\is_int($code) && !\is_string($code)) {
throw new \InvalidArgumentException('Status code has to be an integer');
}
$code = (int) $code;
if ($code < 100 || $code > 599) {
throw new \InvalidArgumentException(\sprintf('Status code has to be an integer between 100 and 599. A status code of %d was given', $code));
}
$new = clone $this;
$new->statusCode = $code;
if ((null === $reasonPhrase || '' === $reasonPhrase) && isset(self::PHRASES[$new->statusCode])) {
$reasonPhrase = self::PHRASES[$new->statusCode];
}
$new->reasonPhrase = $reasonPhrase;
return $new;
}
}

View File

@@ -1,166 +0,0 @@
<?php
declare (strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\UriInterface;
/**
* @author Michael Dowling and contributors to guzzlehttp/psr7
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*/
class ServerRequest implements ServerRequestInterface
{
use \Nyholm\Psr7\MessageTrait;
use \Nyholm\Psr7\RequestTrait;
/** @var array */
private $attributes = [];
/** @var array */
private $cookieParams = [];
/** @var array|object|null */
private $parsedBody;
/** @var array */
private $queryParams = [];
/** @var array */
private $serverParams;
/** @var UploadedFileInterface[] */
private $uploadedFiles = [];
/**
* @param string $method HTTP method
* @param string|UriInterface $uri URI
* @param array $headers Request headers
* @param string|resource|StreamInterface|null $body Request body
* @param string $version Protocol version
* @param array $serverParams Typically the $_SERVER superglobal
*/
public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1', array $serverParams = [])
{
$this->serverParams = $serverParams;
if (!$uri instanceof UriInterface) {
$uri = new \Nyholm\Psr7\Uri($uri);
}
$this->method = $method;
$this->uri = $uri;
$this->setHeaders($headers);
$this->protocol = $version;
\parse_str($uri->getQuery(), $this->queryParams);
if (!$this->hasHeader('Host')) {
$this->updateHostFromUri();
}
// If we got no body, defer initialization of the stream until ServerRequest::getBody()
if ('' !== $body && null !== $body) {
$this->stream = \Nyholm\Psr7\Stream::create($body);
}
}
public function getServerParams() : array
{
return $this->serverParams;
}
public function getUploadedFiles() : array
{
return $this->uploadedFiles;
}
/**
* @return static
*/
public function withUploadedFiles(array $uploadedFiles) : ServerRequestInterface
{
$new = clone $this;
$new->uploadedFiles = $uploadedFiles;
return $new;
}
public function getCookieParams() : array
{
return $this->cookieParams;
}
/**
* @return static
*/
public function withCookieParams(array $cookies) : ServerRequestInterface
{
$new = clone $this;
$new->cookieParams = $cookies;
return $new;
}
public function getQueryParams() : array
{
return $this->queryParams;
}
/**
* @return static
*/
public function withQueryParams(array $query) : ServerRequestInterface
{
$new = clone $this;
$new->queryParams = $query;
return $new;
}
/**
* @return array|object|null
*/
public function getParsedBody()
{
return $this->parsedBody;
}
/**
* @return static
*/
public function withParsedBody($data) : ServerRequestInterface
{
if (!\is_array($data) && !\is_object($data) && null !== $data) {
throw new \InvalidArgumentException('First parameter to withParsedBody MUST be object, array or null');
}
$new = clone $this;
$new->parsedBody = $data;
return $new;
}
public function getAttributes() : array
{
return $this->attributes;
}
/**
* @return mixed
*/
public function getAttribute($attribute, $default = null)
{
if (!\is_string($attribute)) {
throw new \InvalidArgumentException('Attribute name must be a string');
}
if (\false === \array_key_exists($attribute, $this->attributes)) {
return $default;
}
return $this->attributes[$attribute];
}
/**
* @return static
*/
public function withAttribute($attribute, $value) : ServerRequestInterface
{
if (!\is_string($attribute)) {
throw new \InvalidArgumentException('Attribute name must be a string');
}
$new = clone $this;
$new->attributes[$attribute] = $value;
return $new;
}
/**
* @return static
*/
public function withoutAttribute($attribute) : ServerRequestInterface
{
if (!\is_string($attribute)) {
throw new \InvalidArgumentException('Attribute name must be a string');
}
if (\false === \array_key_exists($attribute, $this->attributes)) {
return $this;
}
$new = clone $this;
unset($new->attributes[$attribute]);
return $new;
}
}

View File

@@ -1,311 +0,0 @@
<?php
declare (strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\StreamInterface;
/**
* @author Michael Dowling and contributors to guzzlehttp/psr7
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*/
class Stream implements StreamInterface
{
use \Nyholm\Psr7\StreamTrait;
/** @var resource|null A resource reference */
private $stream;
/** @var bool */
private $seekable;
/** @var bool */
private $readable;
/** @var bool */
private $writable;
/** @var array|mixed|void|bool|null */
private $uri;
/** @var int|null */
private $size;
/** @var array Hash of readable and writable stream types */
private const READ_WRITE_HASH = ['read' => ['r' => \true, 'w+' => \true, 'r+' => \true, 'x+' => \true, 'c+' => \true, 'rb' => \true, 'w+b' => \true, 'r+b' => \true, 'x+b' => \true, 'c+b' => \true, 'rt' => \true, 'w+t' => \true, 'r+t' => \true, 'x+t' => \true, 'c+t' => \true, 'a+' => \true], 'write' => ['w' => \true, 'w+' => \true, 'rw' => \true, 'r+' => \true, 'x+' => \true, 'c+' => \true, 'wb' => \true, 'w+b' => \true, 'r+b' => \true, 'x+b' => \true, 'c+b' => \true, 'w+t' => \true, 'r+t' => \true, 'x+t' => \true, 'c+t' => \true, 'a' => \true, 'a+' => \true]];
/**
* @param resource $body
*/
public function __construct($body)
{
if (!\is_resource($body)) {
throw new \InvalidArgumentException('First argument to Stream::__construct() must be resource');
}
$this->stream = $body;
$meta = \stream_get_meta_data($this->stream);
$this->seekable = $meta['seekable'] && 0 === \fseek($this->stream, 0, \SEEK_CUR);
$this->readable = isset(self::READ_WRITE_HASH['read'][$meta['mode']]);
$this->writable = isset(self::READ_WRITE_HASH['write'][$meta['mode']]);
}
/**
* Creates a new PSR-7 stream.
*
* @param string|resource|StreamInterface $body
*
* @throws \InvalidArgumentException
*/
public static function create($body = '') : StreamInterface
{
if ($body instanceof StreamInterface) {
return $body;
}
if (\is_string($body)) {
if (200000 <= \strlen($body)) {
$body = self::openZvalStream($body);
} else {
$resource = \fopen('php://memory', 'r+');
\fwrite($resource, $body);
\fseek($resource, 0);
$body = $resource;
}
}
if (!\is_resource($body)) {
throw new \InvalidArgumentException('First argument to Stream::create() must be a string, resource or StreamInterface');
}
return new self($body);
}
/**
* Closes the stream when the destructed.
*/
public function __destruct()
{
$this->close();
}
public function close() : void
{
if (isset($this->stream)) {
if (\is_resource($this->stream)) {
\fclose($this->stream);
}
$this->detach();
}
}
public function detach()
{
if (!isset($this->stream)) {
return null;
}
$result = $this->stream;
unset($this->stream);
$this->size = $this->uri = null;
$this->readable = $this->writable = $this->seekable = \false;
return $result;
}
private function getUri()
{
if (\false !== $this->uri) {
$this->uri = $this->getMetadata('uri') ?? \false;
}
return $this->uri;
}
public function getSize() : ?int
{
if (null !== $this->size) {
return $this->size;
}
if (!isset($this->stream)) {
return null;
}
// Clear the stat cache if the stream has a URI
if ($uri = $this->getUri()) {
\clearstatcache(\true, $uri);
}
$stats = \fstat($this->stream);
if (isset($stats['size'])) {
$this->size = $stats['size'];
return $this->size;
}
return null;
}
public function tell() : int
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}
if (\false === ($result = @\ftell($this->stream))) {
throw new \RuntimeException('Unable to determine stream position: ' . (\error_get_last()['message'] ?? ''));
}
return $result;
}
public function eof() : bool
{
return !isset($this->stream) || \feof($this->stream);
}
public function isSeekable() : bool
{
return $this->seekable;
}
public function seek($offset, $whence = \SEEK_SET) : void
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}
if (!$this->seekable) {
throw new \RuntimeException('Stream is not seekable');
}
if (-1 === \fseek($this->stream, $offset, $whence)) {
throw new \RuntimeException('Unable to seek to stream position "' . $offset . '" with whence ' . \var_export($whence, \true));
}
}
public function rewind() : void
{
$this->seek(0);
}
public function isWritable() : bool
{
return $this->writable;
}
public function write($string) : int
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}
if (!$this->writable) {
throw new \RuntimeException('Cannot write to a non-writable stream');
}
// We can't know the size after writing anything
$this->size = null;
if (\false === ($result = @\fwrite($this->stream, $string))) {
throw new \RuntimeException('Unable to write to stream: ' . (\error_get_last()['message'] ?? ''));
}
return $result;
}
public function isReadable() : bool
{
return $this->readable;
}
public function read($length) : string
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}
if (!$this->readable) {
throw new \RuntimeException('Cannot read from non-readable stream');
}
if (\false === ($result = @\fread($this->stream, $length))) {
throw new \RuntimeException('Unable to read from stream: ' . (\error_get_last()['message'] ?? ''));
}
return $result;
}
public function getContents() : string
{
if (!isset($this->stream)) {
throw new \RuntimeException('Stream is detached');
}
$exception = null;
\set_error_handler(static function ($type, $message) use(&$exception) {
throw $exception = new \RuntimeException('Unable to read stream contents: ' . $message);
});
try {
return \stream_get_contents($this->stream);
} catch (\Throwable $e) {
throw $e === $exception ? $e : new \RuntimeException('Unable to read stream contents: ' . $e->getMessage(), 0, $e);
} finally {
\restore_error_handler();
}
}
/**
* @return mixed
*/
public function getMetadata($key = null)
{
if (null !== $key && !\is_string($key)) {
throw new \InvalidArgumentException('Metadata key must be a string');
}
if (!isset($this->stream)) {
return $key ? null : [];
}
$meta = \stream_get_meta_data($this->stream);
if (null === $key) {
return $meta;
}
return $meta[$key] ?? null;
}
private static function openZvalStream(string $body)
{
static $wrapper;
$wrapper ?? \stream_wrapper_register('Nyholm-Psr7-Zval', $wrapper = \get_class(new class
{
public $context;
private $data;
private $position = 0;
public function stream_open() : bool
{
$this->data = \stream_context_get_options($this->context)['Nyholm-Psr7-Zval']['data'];
\stream_context_set_option($this->context, 'Nyholm-Psr7-Zval', 'data', null);
return \true;
}
public function stream_read(int $count) : string
{
$result = \substr($this->data, $this->position, $count);
$this->position += \strlen($result);
return $result;
}
public function stream_write(string $data) : int
{
$this->data = \substr_replace($this->data, $data, $this->position, \strlen($data));
$this->position += \strlen($data);
return \strlen($data);
}
public function stream_tell() : int
{
return $this->position;
}
public function stream_eof() : bool
{
return \strlen($this->data) <= $this->position;
}
public function stream_stat() : array
{
return [
'mode' => 33206,
// POSIX_S_IFREG | 0666
'nlink' => 1,
'rdev' => -1,
'size' => \strlen($this->data),
'blksize' => -1,
'blocks' => -1,
];
}
public function stream_seek(int $offset, int $whence) : bool
{
if (\SEEK_SET === $whence && (0 <= $offset && \strlen($this->data) >= $offset)) {
$this->position = $offset;
} elseif (\SEEK_CUR === $whence && 0 <= $offset) {
$this->position += $offset;
} elseif (\SEEK_END === $whence && (0 > $offset && 0 <= ($offset = \strlen($this->data) + $offset))) {
$this->position = $offset;
} else {
return \false;
}
return \true;
}
public function stream_set_option() : bool
{
return \true;
}
public function stream_truncate(int $new_size) : bool
{
if ($new_size) {
$this->data = \substr($this->data, 0, $new_size);
$this->position = \min($this->position, $new_size);
} else {
$this->data = '';
$this->position = 0;
}
return \true;
}
}));
$context = \stream_context_create(['Nyholm-Psr7-Zval' => ['data' => $body]]);
if (!($stream = @\fopen('Nyholm-Psr7-Zval://', 'r+', \false, $context))) {
\stream_wrapper_register('Nyholm-Psr7-Zval', $wrapper);
$stream = \fopen('Nyholm-Psr7-Zval://', 'r+', \false, $context);
}
return $stream;
}
}

View File

@@ -1,51 +0,0 @@
<?php
declare (strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\StreamInterface;
use PerformanceBooster\Symfony\Component\Debug\ErrorHandler as SymfonyLegacyErrorHandler;
use PerformanceBooster\Symfony\Component\ErrorHandler\ErrorHandler as SymfonyErrorHandler;
if (\PHP_VERSION_ID >= 70400 || (new \ReflectionMethod(StreamInterface::class, '__toString'))->hasReturnType()) {
/**
* @internal
*/
trait StreamTrait
{
public function __toString() : string
{
if ($this->isSeekable()) {
$this->seek(0);
}
return $this->getContents();
}
}
} else {
/**
* @internal
*/
trait StreamTrait
{
/**
* @return string
*/
public function __toString()
{
try {
if ($this->isSeekable()) {
$this->seek(0);
}
return $this->getContents();
} catch (\Throwable $e) {
if (\is_array($errorHandler = \set_error_handler('var_dump'))) {
$errorHandler = $errorHandler[0] ?? null;
}
\restore_error_handler();
if ($e instanceof \Error || $errorHandler instanceof SymfonyErrorHandler || $errorHandler instanceof SymfonyLegacyErrorHandler) {
return \trigger_error((string) $e, \E_USER_ERROR);
}
return '';
}
}
}
}

View File

@@ -1,138 +0,0 @@
<?php
declare (strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
/**
* @author Michael Dowling and contributors to guzzlehttp/psr7
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*/
class UploadedFile implements UploadedFileInterface
{
/** @var array */
private const ERRORS = [\UPLOAD_ERR_OK => 1, \UPLOAD_ERR_INI_SIZE => 1, \UPLOAD_ERR_FORM_SIZE => 1, \UPLOAD_ERR_PARTIAL => 1, \UPLOAD_ERR_NO_FILE => 1, \UPLOAD_ERR_NO_TMP_DIR => 1, \UPLOAD_ERR_CANT_WRITE => 1, \UPLOAD_ERR_EXTENSION => 1];
/** @var string */
private $clientFilename;
/** @var string */
private $clientMediaType;
/** @var int */
private $error;
/** @var string|null */
private $file;
/** @var bool */
private $moved = \false;
/** @var int */
private $size;
/** @var StreamInterface|null */
private $stream;
/**
* @param StreamInterface|string|resource $streamOrFile
* @param int $size
* @param int $errorStatus
* @param string|null $clientFilename
* @param string|null $clientMediaType
*/
public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null)
{
if (\false === \is_int($errorStatus) || !isset(self::ERRORS[$errorStatus])) {
throw new \InvalidArgumentException('Upload file error status must be an integer value and one of the "UPLOAD_ERR_*" constants');
}
if (\false === \is_int($size)) {
throw new \InvalidArgumentException('Upload file size must be an integer');
}
if (null !== $clientFilename && !\is_string($clientFilename)) {
throw new \InvalidArgumentException('Upload file client filename must be a string or null');
}
if (null !== $clientMediaType && !\is_string($clientMediaType)) {
throw new \InvalidArgumentException('Upload file client media type must be a string or null');
}
$this->error = $errorStatus;
$this->size = $size;
$this->clientFilename = $clientFilename;
$this->clientMediaType = $clientMediaType;
if (\UPLOAD_ERR_OK === $this->error) {
// Depending on the value set file or stream variable.
if (\is_string($streamOrFile) && '' !== $streamOrFile) {
$this->file = $streamOrFile;
} elseif (\is_resource($streamOrFile)) {
$this->stream = \Nyholm\Psr7\Stream::create($streamOrFile);
} elseif ($streamOrFile instanceof StreamInterface) {
$this->stream = $streamOrFile;
} else {
throw new \InvalidArgumentException('Invalid stream or file provided for UploadedFile');
}
}
}
/**
* @throws \RuntimeException if is moved or not ok
*/
private function validateActive() : void
{
if (\UPLOAD_ERR_OK !== $this->error) {
throw new \RuntimeException('Cannot retrieve stream due to upload error');
}
if ($this->moved) {
throw new \RuntimeException('Cannot retrieve stream after it has already been moved');
}
}
public function getStream() : StreamInterface
{
$this->validateActive();
if ($this->stream instanceof StreamInterface) {
return $this->stream;
}
if (\false === ($resource = @\fopen($this->file, 'r'))) {
throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $this->file, \error_get_last()['message'] ?? ''));
}
return \Nyholm\Psr7\Stream::create($resource);
}
public function moveTo($targetPath) : void
{
$this->validateActive();
if (!\is_string($targetPath) || '' === $targetPath) {
throw new \InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string');
}
if (null !== $this->file) {
$this->moved = 'cli' === \PHP_SAPI ? @\rename($this->file, $targetPath) : @\move_uploaded_file($this->file, $targetPath);
if (\false === $this->moved) {
throw new \RuntimeException(\sprintf('Uploaded file could not be moved to "%s": %s', $targetPath, \error_get_last()['message'] ?? ''));
}
} else {
$stream = $this->getStream();
if ($stream->isSeekable()) {
$stream->rewind();
}
if (\false === ($resource = @\fopen($targetPath, 'w'))) {
throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $targetPath, \error_get_last()['message'] ?? ''));
}
$dest = \Nyholm\Psr7\Stream::create($resource);
while (!$stream->eof()) {
if (!$dest->write($stream->read(1048576))) {
break;
}
}
$this->moved = \true;
}
}
public function getSize() : int
{
return $this->size;
}
public function getError() : int
{
return $this->error;
}
public function getClientFilename() : ?string
{
return $this->clientFilename;
}
public function getClientMediaType() : ?string
{
return $this->clientMediaType;
}
}

View File

@@ -1,286 +0,0 @@
<?php
declare (strict_types=1);
namespace Nyholm\Psr7;
use Psr\Http\Message\UriInterface;
/**
* PSR-7 URI implementation.
*
* @author Michael Dowling
* @author Tobias Schultze
* @author Matthew Weier O'Phinney
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Martijn van der Ven <martijn@vanderven.se>
*
* @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
*/
class Uri implements UriInterface
{
private const SCHEMES = ['http' => 80, 'https' => 443];
private const CHAR_UNRESERVED = 'a-zA-Z0-9_\\-\\.~';
private const CHAR_SUB_DELIMS = '!\\$&\'\\(\\)\\*\\+,;=';
private const CHAR_GEN_DELIMS = ':\\/\\?#\\[\\]@';
/** @var string Uri scheme. */
private $scheme = '';
/** @var string Uri user info. */
private $userInfo = '';
/** @var string Uri host. */
private $host = '';
/** @var int|null Uri port. */
private $port;
/** @var string Uri path. */
private $path = '';
/** @var string Uri query string. */
private $query = '';
/** @var string Uri fragment. */
private $fragment = '';
public function __construct(string $uri = '')
{
if ('' !== $uri) {
if (\false === ($parts = \parse_url($uri))) {
throw new \InvalidArgumentException(\sprintf('Unable to parse URI: "%s"', $uri));
}
// Apply parse_url parts to a URI.
$this->scheme = isset($parts['scheme']) ? \strtr($parts['scheme'], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') : '';
$this->userInfo = $parts['user'] ?? '';
$this->host = isset($parts['host']) ? \strtr($parts['host'], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') : '';
$this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null;
$this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
$this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : '';
$this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : '';
if (isset($parts['pass'])) {
$this->userInfo .= ':' . $parts['pass'];
}
}
}
public function __toString() : string
{
return self::createUriString($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment);
}
public function getScheme() : string
{
return $this->scheme;
}
public function getAuthority() : string
{
if ('' === $this->host) {
return '';
}
$authority = $this->host;
if ('' !== $this->userInfo) {
$authority = $this->userInfo . '@' . $authority;
}
if (null !== $this->port) {
$authority .= ':' . $this->port;
}
return $authority;
}
public function getUserInfo() : string
{
return $this->userInfo;
}
public function getHost() : string
{
return $this->host;
}
public function getPort() : ?int
{
return $this->port;
}
public function getPath() : string
{
$path = $this->path;
if ('' !== $path && '/' !== $path[0]) {
if ('' !== $this->host) {
// If the path is rootless and an authority is present, the path MUST be prefixed by "/"
$path = '/' . $path;
}
} elseif (isset($path[1]) && '/' === $path[1]) {
// If the path is starting with more than one "/", the
// starting slashes MUST be reduced to one.
$path = '/' . \ltrim($path, '/');
}
return $path;
}
public function getQuery() : string
{
return $this->query;
}
public function getFragment() : string
{
return $this->fragment;
}
/**
* @return static
*/
public function withScheme($scheme) : UriInterface
{
if (!\is_string($scheme)) {
throw new \InvalidArgumentException('Scheme must be a string');
}
if ($this->scheme === ($scheme = \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'))) {
return $this;
}
$new = clone $this;
$new->scheme = $scheme;
$new->port = $new->filterPort($new->port);
return $new;
}
/**
* @return static
*/
public function withUserInfo($user, $password = null) : UriInterface
{
if (!\is_string($user)) {
throw new \InvalidArgumentException('User must be a string');
}
$info = \preg_replace_callback('/[' . self::CHAR_GEN_DELIMS . self::CHAR_SUB_DELIMS . ']++/', [__CLASS__, 'rawurlencodeMatchZero'], $user);
if (null !== $password && '' !== $password) {
if (!\is_string($password)) {
throw new \InvalidArgumentException('Password must be a string');
}
$info .= ':' . \preg_replace_callback('/[' . self::CHAR_GEN_DELIMS . self::CHAR_SUB_DELIMS . ']++/', [__CLASS__, 'rawurlencodeMatchZero'], $password);
}
if ($this->userInfo === $info) {
return $this;
}
$new = clone $this;
$new->userInfo = $info;
return $new;
}
/**
* @return static
*/
public function withHost($host) : UriInterface
{
if (!\is_string($host)) {
throw new \InvalidArgumentException('Host must be a string');
}
if ($this->host === ($host = \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'))) {
return $this;
}
$new = clone $this;
$new->host = $host;
return $new;
}
/**
* @return static
*/
public function withPort($port) : UriInterface
{
if ($this->port === ($port = $this->filterPort($port))) {
return $this;
}
$new = clone $this;
$new->port = $port;
return $new;
}
/**
* @return static
*/
public function withPath($path) : UriInterface
{
if ($this->path === ($path = $this->filterPath($path))) {
return $this;
}
$new = clone $this;
$new->path = $path;
return $new;
}
/**
* @return static
*/
public function withQuery($query) : UriInterface
{
if ($this->query === ($query = $this->filterQueryAndFragment($query))) {
return $this;
}
$new = clone $this;
$new->query = $query;
return $new;
}
/**
* @return static
*/
public function withFragment($fragment) : UriInterface
{
if ($this->fragment === ($fragment = $this->filterQueryAndFragment($fragment))) {
return $this;
}
$new = clone $this;
$new->fragment = $fragment;
return $new;
}
/**
* Create a URI string from its various parts.
*/
private static function createUriString(string $scheme, string $authority, string $path, string $query, string $fragment) : string
{
$uri = '';
if ('' !== $scheme) {
$uri .= $scheme . ':';
}
if ('' !== $authority) {
$uri .= '//' . $authority;
}
if ('' !== $path) {
if ('/' !== $path[0]) {
if ('' !== $authority) {
// If the path is rootless and an authority is present, the path MUST be prefixed by "/"
$path = '/' . $path;
}
} elseif (isset($path[1]) && '/' === $path[1]) {
if ('' === $authority) {
// If the path is starting with more than one "/" and no authority is present, the
// starting slashes MUST be reduced to one.
$path = '/' . \ltrim($path, '/');
}
}
$uri .= $path;
}
if ('' !== $query) {
$uri .= '?' . $query;
}
if ('' !== $fragment) {
$uri .= '#' . $fragment;
}
return $uri;
}
/**
* Is a given port non-standard for the current scheme?
*/
private static function isNonStandardPort(string $scheme, int $port) : bool
{
return !isset(self::SCHEMES[$scheme]) || $port !== self::SCHEMES[$scheme];
}
private function filterPort($port) : ?int
{
if (null === $port) {
return null;
}
$port = (int) $port;
if (0 > $port || 0xffff < $port) {
throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 0 and 65535', $port));
}
return self::isNonStandardPort($this->scheme, $port) ? $port : null;
}
private function filterPath($path) : string
{
if (!\is_string($path)) {
throw new \InvalidArgumentException('Path must be a string');
}
return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\\/]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $path);
}
private function filterQueryAndFragment($str) : string
{
if (!\is_string($str)) {
throw new \InvalidArgumentException('Query and fragment must be a string');
}
return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\\/\\?]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $str);
}
private static function rawurlencodeMatchZero(array $match) : string
{
return \rawurlencode($match[0]);
}
}

View File

@@ -1,105 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\Invoker;
use Closure;
use PerformanceBooster\Invoker\Exception\NotCallableException;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use ReflectionException;
use ReflectionMethod;
/**
* Resolves a callable from a container.
*/
class CallableResolver
{
/** @var ContainerInterface */
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Resolve the given callable into a real PHP callable.
*
* @param callable|string|array $callable
* @return callable Real PHP callable.
* @throws NotCallableException|ReflectionException
*/
public function resolve($callable) : callable
{
if (\is_string($callable) && \strpos($callable, '::') !== \false) {
$callable = \explode('::', $callable, 2);
}
$callable = $this->resolveFromContainer($callable);
if (!\is_callable($callable)) {
throw NotCallableException::fromInvalidCallable($callable, \true);
}
return $callable;
}
/**
* @param callable|string|array $callable
* @return callable|mixed
* @throws NotCallableException|ReflectionException
*/
private function resolveFromContainer($callable)
{
// Shortcut for a very common use case
if ($callable instanceof Closure) {
return $callable;
}
// If it's already a callable there is nothing to do
if (\is_callable($callable)) {
// TODO with PHP 8 that should not be necessary to check this anymore
if (!$this->isStaticCallToNonStaticMethod($callable)) {
return $callable;
}
}
// The callable is a container entry name
if (\is_string($callable)) {
try {
return $this->container->get($callable);
} catch (NotFoundExceptionInterface $e) {
if ($this->container->has($callable)) {
throw $e;
}
throw NotCallableException::fromInvalidCallable($callable, \true);
}
}
// The callable is an array whose first item is a container entry name
// e.g. ['some-container-entry', 'methodToCall']
if (\is_array($callable) && \is_string($callable[0])) {
try {
// Replace the container entry name by the actual object
$callable[0] = $this->container->get($callable[0]);
return $callable;
} catch (NotFoundExceptionInterface $e) {
if ($this->container->has($callable[0])) {
throw $e;
}
throw new NotCallableException(\sprintf('Cannot call %s() on %s because it is not a class nor a valid container entry', $callable[1], $callable[0]));
}
}
// Unrecognized stuff, we let it fail later
return $callable;
}
/**
* Check if the callable represents a static call to a non-static method.
*
* @param mixed $callable
* @throws ReflectionException
*/
private function isStaticCallToNonStaticMethod($callable) : bool
{
if (\is_array($callable) && \is_string($callable[0])) {
[$class, $method] = $callable;
if (!\method_exists($class, $method)) {
return \false;
}
$reflection = new ReflectionMethod($class, $method);
return !$reflection->isStatic();
}
return \false;
}
}

View File

@@ -1,11 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\Invoker\Exception;
/**
* Impossible to invoke the callable.
*/
class InvocationException extends \Exception
{
}

View File

@@ -1,29 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\Invoker\Exception;
/**
* The given callable is not actually callable.
*/
class NotCallableException extends InvocationException
{
/**
* @param mixed $value
*/
public static function fromInvalidCallable($value, bool $containerEntry = \false) : self
{
if (\is_object($value)) {
$message = \sprintf('Instance of %s is not a callable', \get_class($value));
} elseif (\is_array($value) && isset($value[0], $value[1])) {
$class = \is_object($value[0]) ? \get_class($value[0]) : $value[0];
$extra = \method_exists($class, '__call') || \method_exists($class, '__callStatic') ? ' A __call() or __callStatic() method exists but magic methods are not supported.' : '';
$message = \sprintf('%s::%s() is not a callable.%s', $class, $value[1], $extra);
} elseif ($containerEntry) {
$message = \var_export($value, \true) . ' is neither a callable nor a valid container entry';
} else {
$message = \var_export($value, \true) . ' is not a callable';
}
return new self($message);
}
}

View File

@@ -1,11 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\Invoker\Exception;
/**
* Not enough parameters could be resolved to invoke the callable.
*/
class NotEnoughParametersException extends InvocationException
{
}

View File

@@ -1,83 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\Invoker;
use PerformanceBooster\Invoker\Exception\NotCallableException;
use PerformanceBooster\Invoker\Exception\NotEnoughParametersException;
use PerformanceBooster\Invoker\ParameterResolver\AssociativeArrayResolver;
use PerformanceBooster\Invoker\ParameterResolver\DefaultValueResolver;
use PerformanceBooster\Invoker\ParameterResolver\NumericArrayResolver;
use PerformanceBooster\Invoker\ParameterResolver\ParameterResolver;
use PerformanceBooster\Invoker\ParameterResolver\ResolverChain;
use PerformanceBooster\Invoker\Reflection\CallableReflection;
use Psr\Container\ContainerInterface;
use ReflectionParameter;
/**
* Invoke a callable.
*/
class Invoker implements InvokerInterface
{
/** @var CallableResolver|null */
private $callableResolver;
/** @var ParameterResolver */
private $parameterResolver;
/** @var ContainerInterface|null */
private $container;
public function __construct(?ParameterResolver $parameterResolver = null, ?ContainerInterface $container = null)
{
$this->parameterResolver = $parameterResolver ?: $this->createParameterResolver();
$this->container = $container;
if ($container) {
$this->callableResolver = new CallableResolver($container);
}
}
/**
* {@inheritdoc}
*/
public function call($callable, array $parameters = [])
{
if ($this->callableResolver) {
$callable = $this->callableResolver->resolve($callable);
}
if (!\is_callable($callable)) {
throw new NotCallableException(\sprintf('%s is not a callable', \is_object($callable) ? 'Instance of ' . \get_class($callable) : \var_export($callable, \true)));
}
$callableReflection = CallableReflection::create($callable);
$args = $this->parameterResolver->getParameters($callableReflection, $parameters, []);
// Sort by array key because call_user_func_array ignores numeric keys
\ksort($args);
// Check all parameters are resolved
$diff = \array_diff_key($callableReflection->getParameters(), $args);
$parameter = \reset($diff);
if ($parameter && \assert($parameter instanceof ReflectionParameter) && !$parameter->isVariadic()) {
throw new NotEnoughParametersException(\sprintf('Unable to invoke the callable because no value was given for parameter %d ($%s)', $parameter->getPosition() + 1, $parameter->name));
}
return \call_user_func_array($callable, $args);
}
/**
* Create the default parameter resolver.
*/
private function createParameterResolver() : ParameterResolver
{
return new ResolverChain([new NumericArrayResolver(), new AssociativeArrayResolver(), new DefaultValueResolver()]);
}
/**
* @return ParameterResolver By default it's a ResolverChain
*/
public function getParameterResolver() : ParameterResolver
{
return $this->parameterResolver;
}
public function getContainer() : ?ContainerInterface
{
return $this->container;
}
/**
* @return CallableResolver|null Returns null if no container was given in the constructor.
*/
public function getCallableResolver() : ?CallableResolver
{
return $this->callableResolver;
}
}

View File

@@ -1,25 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\Invoker;
use PerformanceBooster\Invoker\Exception\InvocationException;
use PerformanceBooster\Invoker\Exception\NotCallableException;
use PerformanceBooster\Invoker\Exception\NotEnoughParametersException;
/**
* Invoke a callable.
*/
interface InvokerInterface
{
/**
* Call the given function using the given parameters.
*
* @param callable|array|string $callable Function to call.
* @param array $parameters Parameters to use.
* @return mixed Result of the function.
* @throws InvocationException Base exception class for all the sub-exceptions below.
* @throws NotCallableException
* @throws NotEnoughParametersException
*/
public function call($callable, array $parameters = []);
}

View File

@@ -1,31 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\Invoker\ParameterResolver;
use ReflectionFunctionAbstract;
/**
* Tries to map an associative array (string-indexed) to the parameter names.
*
* E.g. `->call($callable, ['foo' => 'bar'])` will inject the string `'bar'`
* in the parameter named `$foo`.
*
* Parameters that are not indexed by a string are ignored.
*/
class AssociativeArrayResolver implements ParameterResolver
{
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
$parameters = $reflection->getParameters();
// Skip parameters already resolved
if (!empty($resolvedParameters)) {
$parameters = \array_diff_key($parameters, $resolvedParameters);
}
foreach ($parameters as $index => $parameter) {
if (\array_key_exists($parameter->name, $providedParameters)) {
$resolvedParameters[$index] = $providedParameters[$parameter->name];
}
}
return $resolvedParameters;
}
}

View File

@@ -1,38 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\Invoker\ParameterResolver\Container;
use PerformanceBooster\Invoker\ParameterResolver\ParameterResolver;
use Psr\Container\ContainerInterface;
use ReflectionFunctionAbstract;
/**
* Inject entries from a DI container using the parameter names.
*/
class ParameterNameContainerResolver implements ParameterResolver
{
/** @var ContainerInterface */
private $container;
/**
* @param ContainerInterface $container The container to get entries from.
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
$parameters = $reflection->getParameters();
// Skip parameters already resolved
if (!empty($resolvedParameters)) {
$parameters = \array_diff_key($parameters, $resolvedParameters);
}
foreach ($parameters as $index => $parameter) {
$name = $parameter->name;
if ($name && $this->container->has($name)) {
$resolvedParameters[$index] = $this->container->get($name);
}
}
return $resolvedParameters;
}
}

View File

@@ -1,55 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\Invoker\ParameterResolver\Container;
use PerformanceBooster\Invoker\ParameterResolver\ParameterResolver;
use Psr\Container\ContainerInterface;
use ReflectionFunctionAbstract;
use ReflectionNamedType;
/**
* Inject entries from a DI container using the type-hints.
*/
class TypeHintContainerResolver implements ParameterResolver
{
/** @var ContainerInterface */
private $container;
/**
* @param ContainerInterface $container The container to get entries from.
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
$parameters = $reflection->getParameters();
// Skip parameters already resolved
if (!empty($resolvedParameters)) {
$parameters = \array_diff_key($parameters, $resolvedParameters);
}
foreach ($parameters as $index => $parameter) {
$parameterType = $parameter->getType();
if (!$parameterType) {
// No type
continue;
}
if (!$parameterType instanceof ReflectionNamedType) {
// Union types are not supported
continue;
}
if ($parameterType->isBuiltin()) {
// Primitive types are not supported
continue;
}
$parameterClass = $parameterType->getName();
if ($parameterClass === 'self') {
$parameterClass = $parameter->getDeclaringClass()->getName();
}
if ($this->container->has($parameterClass)) {
$resolvedParameters[$index] = $this->container->get($parameterClass);
}
}
return $resolvedParameters;
}
}

View File

@@ -1,37 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\Invoker\ParameterResolver;
use ReflectionException;
use ReflectionFunctionAbstract;
/**
* Finds the default value for a parameter, *if it exists*.
*/
class DefaultValueResolver implements ParameterResolver
{
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
$parameters = $reflection->getParameters();
// Skip parameters already resolved
if (!empty($resolvedParameters)) {
$parameters = \array_diff_key($parameters, $resolvedParameters);
}
foreach ($parameters as $index => $parameter) {
\assert($parameter instanceof \ReflectionParameter);
if ($parameter->isDefaultValueAvailable()) {
try {
$resolvedParameters[$index] = $parameter->getDefaultValue();
} catch (ReflectionException $e) {
// Can't get default values from PHP internal classes and functions
}
} else {
$parameterType = $parameter->getType();
if ($parameterType && $parameterType->allowsNull()) {
$resolvedParameters[$index] = null;
}
}
}
return $resolvedParameters;
}
}

View File

@@ -1,32 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\Invoker\ParameterResolver;
use ReflectionFunctionAbstract;
/**
* Simply returns all the values of the $providedParameters array that are
* indexed by the parameter position (i.e. a number).
*
* E.g. `->call($callable, ['foo', 'bar'])` will simply resolve the parameters
* to `['foo', 'bar']`.
*
* Parameters that are not indexed by a number (i.e. parameter position)
* will be ignored.
*/
class NumericArrayResolver implements ParameterResolver
{
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
// Skip parameters already resolved
if (!empty($resolvedParameters)) {
$providedParameters = \array_diff_key($providedParameters, $resolvedParameters);
}
foreach ($providedParameters as $key => $value) {
if (\is_int($key)) {
$resolvedParameters[$key] = $value;
}
}
return $resolvedParameters;
}
}

View File

@@ -1,26 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\Invoker\ParameterResolver;
use ReflectionFunctionAbstract;
/**
* Resolves the parameters to use to call the callable.
*/
interface ParameterResolver
{
/**
* Resolves the parameters to use to call the callable.
*
* `$resolvedParameters` contains parameters that have already been resolved.
*
* Each ParameterResolver must resolve parameters that are not already
* in `$resolvedParameters`. That allows to chain multiple ParameterResolver.
*
* @param ReflectionFunctionAbstract $reflection Reflection object for the callable.
* @param array $providedParameters Parameters provided by the caller.
* @param array $resolvedParameters Parameters resolved (indexed by parameter position).
* @return array
*/
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters);
}

View File

@@ -1,47 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\Invoker\ParameterResolver;
use ReflectionFunctionAbstract;
/**
* Dispatches the call to other resolvers until all parameters are resolved.
*
* Chain of responsibility pattern.
*/
class ResolverChain implements ParameterResolver
{
/** @var ParameterResolver[] */
private $resolvers;
public function __construct(array $resolvers = [])
{
$this->resolvers = $resolvers;
}
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
$reflectionParameters = $reflection->getParameters();
foreach ($this->resolvers as $resolver) {
$resolvedParameters = $resolver->getParameters($reflection, $providedParameters, $resolvedParameters);
$diff = \array_diff_key($reflectionParameters, $resolvedParameters);
if (empty($diff)) {
// Stop traversing: all parameters are resolved
return $resolvedParameters;
}
}
return $resolvedParameters;
}
/**
* Push a parameter resolver after the ones already registered.
*/
public function appendResolver(ParameterResolver $resolver) : void
{
$this->resolvers[] = $resolver;
}
/**
* Insert a parameter resolver before the ones already registered.
*/
public function prependResolver(ParameterResolver $resolver) : void
{
\array_unshift($this->resolvers, $resolver);
}
}

View File

@@ -1,46 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\Invoker\ParameterResolver;
use ReflectionFunctionAbstract;
use ReflectionNamedType;
/**
* Inject entries using type-hints.
*
* Tries to match type-hints with the parameters provided.
*/
class TypeHintResolver implements ParameterResolver
{
public function getParameters(ReflectionFunctionAbstract $reflection, array $providedParameters, array $resolvedParameters) : array
{
$parameters = $reflection->getParameters();
// Skip parameters already resolved
if (!empty($resolvedParameters)) {
$parameters = \array_diff_key($parameters, $resolvedParameters);
}
foreach ($parameters as $index => $parameter) {
$parameterType = $parameter->getType();
if (!$parameterType) {
// No type
continue;
}
if (!$parameterType instanceof ReflectionNamedType) {
// Union types are not supported
continue;
}
if ($parameterType->isBuiltin()) {
// Primitive types are not supported
continue;
}
$parameterClass = $parameterType->getName();
if ($parameterClass === 'self') {
$parameterClass = $parameter->getDeclaringClass()->getName();
}
if (\array_key_exists($parameterClass, $providedParameters)) {
$resolvedParameters[$index] = $providedParameters[$parameterClass];
}
}
return $resolvedParameters;
}
}

View File

@@ -1,47 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\Invoker\Reflection;
use Closure;
use PerformanceBooster\Invoker\Exception\NotCallableException;
use ReflectionException;
use ReflectionFunction;
use ReflectionFunctionAbstract;
use ReflectionMethod;
/**
* Create a reflection object from a callable or a callable-like.
*
* @internal
*/
class CallableReflection
{
/**
* @param callable|array|string $callable Can be a callable or a callable-like.
* @throws NotCallableException|ReflectionException
*/
public static function create($callable) : ReflectionFunctionAbstract
{
// Closure
if ($callable instanceof Closure) {
return new ReflectionFunction($callable);
}
// Array callable
if (\is_array($callable)) {
[$class, $method] = $callable;
if (!\method_exists($class, $method)) {
throw NotCallableException::fromInvalidCallable($callable);
}
return new ReflectionMethod($class, $method);
}
// Callable object (i.e. implementing __invoke())
if (\is_object($callable) && \method_exists($callable, '__invoke')) {
return new ReflectionMethod($callable, '__invoke');
}
// Standard function
if (\is_string($callable) && \function_exists($callable)) {
return new ReflectionFunction($callable);
}
throw new NotCallableException(\sprintf('%s is not a callable', \is_string($callable) ? $callable : 'Instance of ' . \get_class($callable)));
}
}

View File

@@ -1,63 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Attribute;
use Attribute;
use PerformanceBooster\DI\Definition\Exception\InvalidAttribute;
/**
* #[Inject] attribute.
*
* Marks a property or method as an injection point
*
* @api
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
#[\Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD | Attribute::TARGET_PARAMETER)]
class Inject
{
/**
* Entry name.
*/
private ?string $name = null;
/**
* Parameters, indexed by the parameter number (index) or name.
*
* Used if the attribute is set on a method
*/
private array $parameters = [];
/**
* @throws InvalidAttribute
*/
public function __construct(string|array|null $name = null)
{
// #[Inject('foo')] or #[Inject(name: 'foo')]
if (\is_string($name)) {
$this->name = $name;
}
// #[Inject([...])] on a method
if (\is_array($name)) {
foreach ($name as $key => $value) {
if (!\is_string($value)) {
throw new InvalidAttribute(\sprintf("#[Inject(['param' => 'value'])] expects \"value\" to be a string, %s given.", \json_encode($value, \JSON_THROW_ON_ERROR)));
}
$this->parameters[$key] = $value;
}
}
}
/**
* @return string|null Name of the entry to inject
*/
public function getName() : ?string
{
return $this->name;
}
/**
* @return array Parameters, indexed by the parameter number (index) or name
*/
public function getParameters() : array
{
return $this->parameters;
}
}

View File

@@ -1,30 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Attribute;
use Attribute;
/**
* "Injectable" attribute.
*
* Marks a class as injectable
*
* @api
*
* @author Domenic Muskulus <domenic@muskulus.eu>
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
#[\Attribute(Attribute::TARGET_CLASS)]
class Injectable
{
/**
* @param bool|null $lazy Should the object be lazy-loaded.
*/
public function __construct(private ?bool $lazy = null)
{
}
public function isLazy() : ?bool
{
return $this->lazy;
}
}

View File

@@ -1,93 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI;
use PerformanceBooster\DI\Compiler\RequestedEntryHolder;
use PerformanceBooster\DI\Definition\Definition;
use PerformanceBooster\DI\Definition\Exception\InvalidDefinition;
use PerformanceBooster\DI\Invoker\FactoryParameterResolver;
use PerformanceBooster\Invoker\Exception\NotCallableException;
use PerformanceBooster\Invoker\Exception\NotEnoughParametersException;
use PerformanceBooster\Invoker\Invoker;
use PerformanceBooster\Invoker\InvokerInterface;
use PerformanceBooster\Invoker\ParameterResolver\AssociativeArrayResolver;
use PerformanceBooster\Invoker\ParameterResolver\DefaultValueResolver;
use PerformanceBooster\Invoker\ParameterResolver\NumericArrayResolver;
use PerformanceBooster\Invoker\ParameterResolver\ResolverChain;
/**
* Compiled version of the dependency injection container.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
abstract class CompiledContainer extends Container
{
/**
* This const is overridden in child classes (compiled containers).
* @var array
*/
protected const METHOD_MAPPING = [];
private ?InvokerInterface $factoryInvoker = null;
public function get(string $id) : mixed
{
// Try to find the entry in the singleton map
if (isset($this->resolvedEntries[$id]) || \array_key_exists($id, $this->resolvedEntries)) {
return $this->resolvedEntries[$id];
}
/** @psalm-suppress UndefinedConstant */
$method = static::METHOD_MAPPING[$id] ?? null;
// If it's a compiled entry, then there is a method in this class
if ($method !== null) {
// Check if we are already getting this entry -> circular dependency
if (isset($this->entriesBeingResolved[$id])) {
$idList = \implode(' -> ', [...\array_keys($this->entriesBeingResolved), $id]);
throw new DependencyException("Circular dependency detected while trying to resolve entry '{$id}': Dependencies: " . $idList);
}
$this->entriesBeingResolved[$id] = \true;
try {
$value = $this->{$method}();
} finally {
unset($this->entriesBeingResolved[$id]);
}
// Store the entry to always return it without recomputing it
$this->resolvedEntries[$id] = $value;
return $value;
}
return parent::get($id);
}
public function has(string $id) : bool
{
// The parent method is overridden to check in our array, it avoids resolving definitions
/** @psalm-suppress UndefinedConstant */
if (isset(static::METHOD_MAPPING[$id])) {
return \true;
}
return parent::has($id);
}
protected function setDefinition(string $name, Definition $definition) : void
{
// It needs to be forbidden because that would mean get() must go through the definitions
// every time, which kinds of defeats the performance gains of the compiled container
throw new \LogicException('You cannot set a definition at runtime on a compiled container. You can either put your definitions in a file, disable compilation or ->set() a raw value directly (PHP object, string, int, ...) instead of a PHP-DI definition.');
}
/**
* Invoke the given callable.
*/
protected function resolveFactory($callable, $entryName, array $extraParameters = []) : mixed
{
// Initialize the factory resolver
if (!$this->factoryInvoker) {
$parameterResolver = new ResolverChain([new AssociativeArrayResolver(), new FactoryParameterResolver($this->delegateContainer), new NumericArrayResolver(), new DefaultValueResolver()]);
$this->factoryInvoker = new Invoker($parameterResolver, $this->delegateContainer);
}
$parameters = [$this->delegateContainer, new RequestedEntryHolder($entryName)];
$parameters = \array_merge($parameters, $extraParameters);
try {
return $this->factoryInvoker->call($callable, $parameters);
} catch (NotCallableException $e) {
throw new InvalidDefinition("Entry \"{$entryName}\" cannot be resolved: factory " . $e->getMessage());
} catch (NotEnoughParametersException $e) {
throw new InvalidDefinition("Entry \"{$entryName}\" cannot be resolved: " . $e->getMessage());
}
}
}

View File

@@ -1,324 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Compiler;
use function chmod;
use PerformanceBooster\DI\Definition\ArrayDefinition;
use PerformanceBooster\DI\Definition\DecoratorDefinition;
use PerformanceBooster\DI\Definition\Definition;
use PerformanceBooster\DI\Definition\EnvironmentVariableDefinition;
use PerformanceBooster\DI\Definition\Exception\InvalidDefinition;
use PerformanceBooster\DI\Definition\FactoryDefinition;
use PerformanceBooster\DI\Definition\ObjectDefinition;
use PerformanceBooster\DI\Definition\Reference;
use PerformanceBooster\DI\Definition\Source\DefinitionSource;
use PerformanceBooster\DI\Definition\StringDefinition;
use PerformanceBooster\DI\Definition\ValueDefinition;
use PerformanceBooster\DI\DependencyException;
use PerformanceBooster\DI\Proxy\ProxyFactoryInterface;
use function dirname;
use function file_put_contents;
use InvalidArgumentException;
use PerformanceBooster\Laravel\SerializableClosure\Support\ReflectionClosure;
use function rename;
use function sprintf;
use function tempnam;
use function unlink;
/**
* Compiles the container into PHP code much more optimized for performances.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class Compiler
{
private string $containerClass;
private string $containerParentClass;
/**
* Definitions indexed by the entry name. The value can be null if the definition needs to be fetched.
*
* Keys are strings, values are `Definition` objects or null.
*/
private \ArrayIterator $entriesToCompile;
/**
* Progressive counter for definitions.
*
* Each key in $entriesToCompile is defined as 'SubEntry' + counter
* and each definition has always the same key in the CompiledContainer
* if PHP-DI configuration does not change.
*/
private int $subEntryCounter = 0;
/**
* Progressive counter for CompiledContainer get methods.
*
* Each CompiledContainer method name is defined as 'get' + counter
* and remains the same after each recompilation
* if PHP-DI configuration does not change.
*/
private int $methodMappingCounter = 0;
/**
* Map of entry names to method names.
*
* @var string[]
*/
private array $entryToMethodMapping = [];
/**
* @var string[]
*/
private array $methods = [];
private bool $autowiringEnabled;
public function __construct(private ProxyFactoryInterface $proxyFactory)
{
}
public function getProxyFactory() : ProxyFactoryInterface
{
return $this->proxyFactory;
}
/**
* Compile the container.
*
* @return string The compiled container file name.
*/
public function compile(DefinitionSource $definitionSource, string $directory, string $className, string $parentClassName, bool $autowiringEnabled) : string
{
$fileName = \rtrim($directory, '/') . '/' . $className . '.php';
if (\file_exists($fileName)) {
// The container is already compiled
return $fileName;
}
$this->autowiringEnabled = $autowiringEnabled;
// Validate that a valid class name was provided
$validClassName = \preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $className);
if (!$validClassName) {
throw new InvalidArgumentException("The container cannot be compiled: `{$className}` is not a valid PHP class name");
}
$this->entriesToCompile = new \ArrayIterator($definitionSource->getDefinitions());
// We use an ArrayIterator so that we can keep adding new items to the list while we compile entries
foreach ($this->entriesToCompile as $entryName => $definition) {
$silenceErrors = \false;
// This is an entry found by reference during autowiring
if (!$definition) {
$definition = $definitionSource->getDefinition($entryName);
// We silence errors for those entries because type-hints may reference interfaces/abstract classes
// which could later be defined, or even not used (we don't want to block the compilation for those)
$silenceErrors = \true;
}
if (!$definition) {
// We do not throw a `NotFound` exception here because the dependency
// could be defined at runtime
continue;
}
// Check that the definition can be compiled
$errorMessage = $this->isCompilable($definition);
if ($errorMessage !== \true) {
continue;
}
try {
$this->compileDefinition($entryName, $definition);
} catch (InvalidDefinition $e) {
if ($silenceErrors) {
// forget the entry
unset($this->entryToMethodMapping[$entryName]);
} else {
throw $e;
}
}
}
$this->containerClass = $className;
$this->containerParentClass = $parentClassName;
\ob_start();
require __DIR__ . '/Template.php';
$fileContent = \ob_get_clean();
$fileContent = "<?php\n" . $fileContent;
$this->createCompilationDirectory(dirname($fileName));
$this->writeFileAtomic($fileName, $fileContent);
return $fileName;
}
private function writeFileAtomic(string $fileName, string $content) : void
{
$tmpFile = @tempnam(dirname($fileName), 'swap-compile');
if ($tmpFile === \false) {
throw new InvalidArgumentException(sprintf('Error while creating temporary file in %s', dirname($fileName)));
}
@chmod($tmpFile, 0666);
$written = file_put_contents($tmpFile, $content);
if ($written === \false) {
@unlink($tmpFile);
throw new InvalidArgumentException(sprintf('Error while writing to %s', $tmpFile));
}
@chmod($tmpFile, 0666);
$renamed = @rename($tmpFile, $fileName);
if (!$renamed) {
@unlink($tmpFile);
throw new InvalidArgumentException(sprintf('Error while renaming %s to %s', $tmpFile, $fileName));
}
}
/**
* @return string The method name
* @throws DependencyException
* @throws InvalidDefinition
*/
private function compileDefinition(string $entryName, Definition $definition) : string
{
// Generate a unique method name
$methodName = 'get' . ++$this->methodMappingCounter;
$this->entryToMethodMapping[$entryName] = $methodName;
switch (\true) {
case $definition instanceof ValueDefinition:
$value = $definition->getValue();
$code = 'return ' . $this->compileValue($value) . ';';
break;
case $definition instanceof Reference:
$targetEntryName = $definition->getTargetEntryName();
$code = 'return $this->delegateContainer->get(' . $this->compileValue($targetEntryName) . ');';
// If this method is not yet compiled we store it for compilation
if (!isset($this->entriesToCompile[$targetEntryName])) {
$this->entriesToCompile[$targetEntryName] = null;
}
break;
case $definition instanceof StringDefinition:
$entryName = $this->compileValue($definition->getName());
$expression = $this->compileValue($definition->getExpression());
$code = 'return \\DI\\Definition\\StringDefinition::resolveExpression(' . $entryName . ', ' . $expression . ', $this->delegateContainer);';
break;
case $definition instanceof EnvironmentVariableDefinition:
$variableName = $this->compileValue($definition->getVariableName());
$isOptional = $this->compileValue($definition->isOptional());
$defaultValue = $this->compileValue($definition->getDefaultValue());
$code = <<<PHP
\$value = \$_ENV[{$variableName}] ?? \$_SERVER[{$variableName}] ?? getenv({$variableName});
if (false !== \$value) return \$value;
if (!{$isOptional}) {
throw new \\DI\\Definition\\Exception\\InvalidDefinition("The environment variable '{$definition->getVariableName()}' has not been defined");
}
return {$defaultValue};
PHP;
break;
case $definition instanceof ArrayDefinition:
try {
$code = 'return ' . $this->compileValue($definition->getValues()) . ';';
} catch (\Exception $e) {
throw new DependencyException(sprintf('Error while compiling %s. %s', $definition->getName(), $e->getMessage()), 0, $e);
}
break;
case $definition instanceof ObjectDefinition:
$compiler = new ObjectCreationCompiler($this);
$code = $compiler->compile($definition);
$code .= "\n return \$object;";
break;
case $definition instanceof DecoratorDefinition:
$decoratedDefinition = $definition->getDecoratedDefinition();
if (!$decoratedDefinition instanceof Definition) {
if (!$definition->getName()) {
throw new InvalidDefinition('Decorators cannot be nested in another definition');
}
throw new InvalidDefinition(sprintf('Entry "%s" decorates nothing: no previous definition with the same name was found', $definition->getName()));
}
$code = sprintf('return call_user_func(%s, %s, $this->delegateContainer);', $this->compileValue($definition->getCallable()), $this->compileValue($decoratedDefinition));
break;
case $definition instanceof FactoryDefinition:
$value = $definition->getCallable();
// Custom error message to help debugging
$isInvokableClass = \is_string($value) && \class_exists($value) && \method_exists($value, '__invoke');
if ($isInvokableClass && !$this->autowiringEnabled) {
throw new InvalidDefinition(sprintf('Entry "%s" cannot be compiled. Invokable classes cannot be automatically resolved if autowiring is disabled on the container, you need to enable autowiring or define the entry manually.', $entryName));
}
$definitionParameters = '';
if (!empty($definition->getParameters())) {
$definitionParameters = ', ' . $this->compileValue($definition->getParameters());
}
$code = sprintf('return $this->resolveFactory(%s, %s%s);', $this->compileValue($value), \var_export($entryName, \true), $definitionParameters);
break;
default:
// This case should not happen (so it cannot be tested)
throw new \Exception('Cannot compile definition of type ' . $definition::class);
}
$this->methods[$methodName] = $code;
return $methodName;
}
public function compileValue(mixed $value) : string
{
// Check that the value can be compiled
$errorMessage = $this->isCompilable($value);
if ($errorMessage !== \true) {
throw new InvalidDefinition($errorMessage);
}
if ($value instanceof Definition) {
// Give it an arbitrary unique name
$subEntryName = 'subEntry' . ++$this->subEntryCounter;
// Compile the sub-definition in another method
$methodName = $this->compileDefinition($subEntryName, $value);
// The value is now a method call to that method (which returns the value)
return "\$this->{$methodName}()";
}
if (\is_array($value)) {
$value = \array_map(function ($value, $key) {
$compiledValue = $this->compileValue($value);
$key = \var_export($key, \true);
return " {$key} => {$compiledValue},\n";
}, $value, \array_keys($value));
$value = \implode('', $value);
return "[\n{$value} ]";
}
if ($value instanceof \Closure) {
return $this->compileClosure($value);
}
return \var_export($value, \true);
}
private function createCompilationDirectory(string $directory) : void
{
if (!\is_dir($directory) && !@\mkdir($directory, 0777, \true) && !\is_dir($directory)) {
throw new InvalidArgumentException(sprintf('Compilation directory does not exist and cannot be created: %s.', $directory));
}
if (!\is_writable($directory)) {
throw new InvalidArgumentException(sprintf('Compilation directory is not writable: %s.', $directory));
}
}
/**
* @return string|true If true is returned that means that the value is compilable.
*/
private function isCompilable($value) : string|bool
{
if ($value instanceof ValueDefinition) {
return $this->isCompilable($value->getValue());
}
if ($value instanceof DecoratorDefinition && empty($value->getName())) {
return 'Decorators cannot be nested in another definition';
}
// All other definitions are compilable
if ($value instanceof Definition) {
return \true;
}
if ($value instanceof \Closure) {
return \true;
}
/** @psalm-suppress UndefinedClass */
if (\PHP_VERSION_ID >= 80100 && $value instanceof \UnitEnum) {
return \true;
}
if (\is_object($value)) {
return 'An object was found but objects cannot be compiled';
}
if (\is_resource($value)) {
return 'A resource was found but resources cannot be compiled';
}
return \true;
}
/**
* @throws InvalidDefinition
*/
private function compileClosure(\Closure $closure) : string
{
$reflector = new ReflectionClosure($closure);
if ($reflector->getUseVariables()) {
throw new InvalidDefinition('Cannot compile closures which import variables using the `use` keyword');
}
if ($reflector->isBindingRequired() || $reflector->isScopeRequired()) {
throw new InvalidDefinition('Cannot compile closures which use $this or self/static/parent references');
}
// Force all closures to be static (add the `static` keyword), i.e. they can't use
// $this, which makes sense since their code is copied into another class.
$code = ($reflector->isStatic() ? '' : 'static ') . $reflector->getCode();
return \trim($code, "\t\n\r;");
}
}

View File

@@ -1,138 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Compiler;
use PerformanceBooster\DI\Definition\Exception\InvalidDefinition;
use PerformanceBooster\DI\Definition\ObjectDefinition;
use PerformanceBooster\DI\Definition\ObjectDefinition\MethodInjection;
use ReflectionClass;
use ReflectionMethod;
use ReflectionParameter;
use ReflectionProperty;
/**
* Compiles an object definition into native PHP code that, when executed, creates the object.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ObjectCreationCompiler
{
public function __construct(private Compiler $compiler)
{
}
public function compile(ObjectDefinition $definition) : string
{
$this->assertClassIsNotAnonymous($definition);
$this->assertClassIsInstantiable($definition);
/** @var class-string $className At this point we have checked the class is valid */
$className = $definition->getClassName();
// Lazy?
if ($definition->isLazy()) {
return $this->compileLazyDefinition($definition);
}
try {
$classReflection = new ReflectionClass($className);
$constructorArguments = $this->resolveParameters($definition->getConstructorInjection(), $classReflection->getConstructor());
$dumpedConstructorArguments = \array_map(function ($value) {
return $this->compiler->compileValue($value);
}, $constructorArguments);
$code = [];
$code[] = \sprintf('$object = new %s(%s);', $className, \implode(', ', $dumpedConstructorArguments));
// Property injections
foreach ($definition->getPropertyInjections() as $propertyInjection) {
$value = $propertyInjection->getValue();
$value = $this->compiler->compileValue($value);
$propertyClassName = $propertyInjection->getClassName() ?: $className;
$property = new ReflectionProperty($propertyClassName, $propertyInjection->getPropertyName());
if ($property->isPublic() && !(\PHP_VERSION_ID >= 80100 && $property->isReadOnly())) {
$code[] = \sprintf('$object->%s = %s;', $propertyInjection->getPropertyName(), $value);
} else {
// Private/protected/readonly property
$code[] = \sprintf('\\DI\\Definition\\Resolver\\ObjectCreator::setPrivatePropertyValue(%s, $object, \'%s\', %s);', \var_export($propertyInjection->getClassName(), \true), $propertyInjection->getPropertyName(), $value);
}
}
// Method injections
foreach ($definition->getMethodInjections() as $methodInjection) {
$methodReflection = new ReflectionMethod($className, $methodInjection->getMethodName());
$parameters = $this->resolveParameters($methodInjection, $methodReflection);
$dumpedParameters = \array_map(function ($value) {
return $this->compiler->compileValue($value);
}, $parameters);
$code[] = \sprintf('$object->%s(%s);', $methodInjection->getMethodName(), \implode(', ', $dumpedParameters));
}
} catch (InvalidDefinition $e) {
throw InvalidDefinition::create($definition, \sprintf('Entry "%s" cannot be compiled: %s', $definition->getName(), $e->getMessage()));
}
return \implode("\n ", $code);
}
public function resolveParameters(?MethodInjection $definition, ?ReflectionMethod $method) : array
{
$args = [];
if (!$method) {
return $args;
}
$definitionParameters = $definition ? $definition->getParameters() : [];
foreach ($method->getParameters() as $index => $parameter) {
if (\array_key_exists($index, $definitionParameters)) {
// Look in the definition
$value =& $definitionParameters[$index];
} elseif ($parameter->isOptional()) {
// If the parameter is optional and wasn't specified, we take its default value
$args[] = $this->getParameterDefaultValue($parameter, $method);
continue;
} else {
throw new InvalidDefinition(\sprintf('Parameter $%s of %s has no value defined or guessable', $parameter->getName(), $this->getFunctionName($method)));
}
$args[] =& $value;
}
return $args;
}
private function compileLazyDefinition(ObjectDefinition $definition) : string
{
$subDefinition = clone $definition;
$subDefinition->setLazy(\false);
$subDefinition = $this->compiler->compileValue($subDefinition);
/** @var class-string $className At this point we have checked the class is valid */
$className = $definition->getClassName();
$this->compiler->getProxyFactory()->generateProxyClass($className);
return <<<STR
\$object = \$this->proxyFactory->createProxy(
'{$definition->getClassName()}',
function () {
return {$subDefinition};
}
);
STR;
}
/**
* Returns the default value of a function parameter.
*
* @throws InvalidDefinition Can't get default values from PHP internal classes and functions
*/
private function getParameterDefaultValue(ReflectionParameter $parameter, ReflectionMethod $function) : mixed
{
try {
return $parameter->getDefaultValue();
} catch (\ReflectionException) {
throw new InvalidDefinition(\sprintf('The parameter "%s" of %s has no type defined or guessable. It has a default value, ' . 'but the default value can\'t be read through Reflection because it is a PHP internal class.', $parameter->getName(), $this->getFunctionName($function)));
}
}
private function getFunctionName(ReflectionMethod $method) : string
{
return $method->getName() . '()';
}
private function assertClassIsNotAnonymous(ObjectDefinition $definition) : void
{
if (\str_contains($definition->getClassName(), '@')) {
throw InvalidDefinition::create($definition, \sprintf('Entry "%s" cannot be compiled: anonymous classes cannot be compiled', $definition->getName()));
}
}
private function assertClassIsInstantiable(ObjectDefinition $definition) : void
{
if ($definition->isInstantiable()) {
return;
}
$message = !$definition->classExists() ? 'Entry "%s" cannot be compiled: the class doesn\'t exist' : 'Entry "%s" cannot be compiled: the class is not instantiable';
throw InvalidDefinition::create($definition, \sprintf($message, $definition->getName()));
}
}

View File

@@ -1,19 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Compiler;
use PerformanceBooster\DI\Factory\RequestedEntry;
/**
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class RequestedEntryHolder implements RequestedEntry
{
public function __construct(private string $name)
{
}
public function getName() : string
{
return $this->name;
}
}

View File

@@ -1,33 +0,0 @@
/**
* This class has been auto-generated by PHP-DI.
*/
class <?php
namespace {
echo $this->containerClass;
?> extends <?php
echo $this->containerParentClass;
?>
{
const METHOD_MAPPING = <?php
\var_export($this->entryToMethodMapping);
?>;
<?php
foreach ($this->methods as $methodName => $methodContent) {
?>
protected function <?php
echo $methodName;
?>()
{
<?php
echo $methodContent;
?>
}
<?php
}
?>
}
<?php
}

View File

@@ -1,327 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI;
use PerformanceBooster\DI\Definition\Definition;
use PerformanceBooster\DI\Definition\Exception\InvalidDefinition;
use PerformanceBooster\DI\Definition\FactoryDefinition;
use PerformanceBooster\DI\Definition\Helper\DefinitionHelper;
use PerformanceBooster\DI\Definition\InstanceDefinition;
use PerformanceBooster\DI\Definition\ObjectDefinition;
use PerformanceBooster\DI\Definition\Resolver\DefinitionResolver;
use PerformanceBooster\DI\Definition\Resolver\ResolverDispatcher;
use PerformanceBooster\DI\Definition\Source\DefinitionArray;
use PerformanceBooster\DI\Definition\Source\MutableDefinitionSource;
use PerformanceBooster\DI\Definition\Source\ReflectionBasedAutowiring;
use PerformanceBooster\DI\Definition\Source\SourceChain;
use PerformanceBooster\DI\Definition\ValueDefinition;
use PerformanceBooster\DI\Invoker\DefinitionParameterResolver;
use PerformanceBooster\DI\Proxy\NativeProxyFactory;
use PerformanceBooster\DI\Proxy\ProxyFactory;
use PerformanceBooster\DI\Proxy\ProxyFactoryInterface;
use InvalidArgumentException;
use PerformanceBooster\Invoker\Invoker;
use PerformanceBooster\Invoker\InvokerInterface;
use PerformanceBooster\Invoker\ParameterResolver\AssociativeArrayResolver;
use PerformanceBooster\Invoker\ParameterResolver\Container\TypeHintContainerResolver;
use PerformanceBooster\Invoker\ParameterResolver\DefaultValueResolver;
use PerformanceBooster\Invoker\ParameterResolver\NumericArrayResolver;
use PerformanceBooster\Invoker\ParameterResolver\ResolverChain;
use Psr\Container\ContainerInterface;
/**
* Dependency Injection Container.
*
* @api
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class Container implements ContainerInterface, FactoryInterface, InvokerInterface
{
/**
* Map of entries that are already resolved.
*/
protected array $resolvedEntries = [];
private MutableDefinitionSource $definitionSource;
private DefinitionResolver $definitionResolver;
/**
* Map of definitions that are already fetched (local cache).
*
* @var array<Definition|null>
*/
private array $fetchedDefinitions = [];
/**
* Array of entries being resolved. Used to avoid circular dependencies and infinite loops.
*/
protected array $entriesBeingResolved = [];
private ?InvokerInterface $invoker = null;
/**
* Container that wraps this container. If none, points to $this.
*/
protected ContainerInterface $delegateContainer;
protected ProxyFactoryInterface $proxyFactory;
public static function create(array $definitions) : static
{
$source = new SourceChain([new ReflectionBasedAutowiring()]);
$source->setMutableDefinitionSource(new DefinitionArray($definitions, new ReflectionBasedAutowiring()));
return new static($definitions);
}
/**
* Use `$container = new Container()` if you want a container with the default configuration.
*
* If you want to customize the container's behavior, you are discouraged to create and pass the
* dependencies yourself, the ContainerBuilder class is here to help you instead.
*
* @see ContainerBuilder
*
* @param ContainerInterface $wrapperContainer If the container is wrapped by another container.
*/
public function __construct(array|MutableDefinitionSource $definitions = [], ?ProxyFactoryInterface $proxyFactory = null, ?ContainerInterface $wrapperContainer = null)
{
if (\is_array($definitions)) {
$this->definitionSource = $this->createDefaultDefinitionSource($definitions);
} else {
$this->definitionSource = $definitions;
}
$this->delegateContainer = $wrapperContainer ?: $this;
if ($proxyFactory === null) {
$proxyFactory = \PHP_VERSION_ID >= 80400 ? new NativeProxyFactory() : new ProxyFactory();
}
$this->proxyFactory = $proxyFactory;
$this->definitionResolver = new ResolverDispatcher($this->delegateContainer, $this->proxyFactory);
// Auto-register the container
$this->resolvedEntries = [self::class => $this, ContainerInterface::class => $this->delegateContainer, FactoryInterface::class => $this, InvokerInterface::class => $this];
}
/**
* Returns an entry of the container by its name.
*
* @template T
* @param string|class-string<T> $id Entry name or a class name.
*
* @return mixed|T
* @throws DependencyException Error while resolving the entry.
* @throws NotFoundException No entry found for the given name.
*/
public function get(string $id) : mixed
{
// If the entry is already resolved we return it
if (isset($this->resolvedEntries[$id]) || \array_key_exists($id, $this->resolvedEntries)) {
return $this->resolvedEntries[$id];
}
$definition = $this->getDefinition($id);
if (!$definition) {
throw new NotFoundException("No entry or class found for '{$id}'");
}
$value = $this->resolveDefinition($definition);
$this->resolvedEntries[$id] = $value;
return $value;
}
private function getDefinition(string $name) : ?Definition
{
// Local cache that avoids fetching the same definition twice
if (!\array_key_exists($name, $this->fetchedDefinitions)) {
$this->fetchedDefinitions[$name] = $this->definitionSource->getDefinition($name);
}
return $this->fetchedDefinitions[$name];
}
/**
* Build an entry of the container by its name.
*
* This method behave like get() except resolves the entry again every time.
* For example if the entry is a class then a new instance will be created each time.
*
* This method makes the container behave like a factory.
*
* @template T
* @param string|class-string<T> $name Entry name or a class name.
* @param array $parameters Optional parameters to use to build the entry. Use this to force
* specific parameters to specific values. Parameters not defined in this
* array will be resolved using the container.
*
* @return mixed|T
* @throws InvalidArgumentException The name parameter must be of type string.
* @throws DependencyException Error while resolving the entry.
* @throws NotFoundException No entry found for the given name.
*/
public function make(string $name, array $parameters = []) : mixed
{
$definition = $this->getDefinition($name);
if (!$definition) {
// If the entry is already resolved we return it
if (\array_key_exists($name, $this->resolvedEntries)) {
return $this->resolvedEntries[$name];
}
throw new NotFoundException("No entry or class found for '{$name}'");
}
return $this->resolveDefinition($definition, $parameters);
}
public function has(string $id) : bool
{
if (\array_key_exists($id, $this->resolvedEntries)) {
return \true;
}
$definition = $this->getDefinition($id);
if ($definition === null) {
return \false;
}
return $this->definitionResolver->isResolvable($definition);
}
/**
* Inject all dependencies on an existing instance.
*
* @template T
* @param object|T $instance Object to perform injection upon
* @return object|T $instance Returns the same instance
* @throws InvalidArgumentException
* @throws DependencyException Error while injecting dependencies
*/
public function injectOn(object $instance) : object
{
$className = $instance::class;
// If the class is anonymous, don't cache its definition
// Checking for anonymous classes is cleaner via Reflection, but also slower
$objectDefinition = \str_contains($className, '@anonymous') ? $this->definitionSource->getDefinition($className) : $this->getDefinition($className);
if (!$objectDefinition instanceof ObjectDefinition) {
return $instance;
}
$definition = new InstanceDefinition($instance, $objectDefinition);
$this->definitionResolver->resolve($definition);
return $instance;
}
/**
* Call the given function using the given parameters.
*
* Missing parameters will be resolved from the container.
*
* @param callable|array|string $callable Function to call.
* @param array $parameters Parameters to use. Can be indexed by the parameter names
* or not indexed (same order as the parameters).
* The array can also contain DI definitions, e.g. DI\get().
*
* @return mixed Result of the function.
*/
public function call($callable, array $parameters = []) : mixed
{
return $this->getInvoker()->call($callable, $parameters);
}
/**
* Define an object or a value in the container.
*
* @param string $name Entry name
* @param mixed|DefinitionHelper $value Value, use definition helpers to define objects
*/
public function set(string $name, mixed $value) : void
{
if ($value instanceof DefinitionHelper) {
$value = $value->getDefinition($name);
} elseif ($value instanceof \Closure) {
$value = new FactoryDefinition($name, $value);
}
if ($value instanceof ValueDefinition) {
$this->resolvedEntries[$name] = $value->getValue();
} elseif ($value instanceof Definition) {
$value->setName($name);
$this->setDefinition($name, $value);
} else {
$this->resolvedEntries[$name] = $value;
}
}
/**
* Get defined container entries.
*
* @return string[]
*/
public function getKnownEntryNames() : array
{
$entries = \array_unique(\array_merge(\array_keys($this->definitionSource->getDefinitions()), \array_keys($this->resolvedEntries)));
\sort($entries);
return $entries;
}
/**
* Get entry debug information.
*
* @param string $name Entry name
*
* @throws InvalidDefinition
* @throws NotFoundException
*/
public function debugEntry(string $name) : string
{
$definition = $this->definitionSource->getDefinition($name);
if ($definition instanceof Definition) {
return (string) $definition;
}
if (\array_key_exists($name, $this->resolvedEntries)) {
return $this->getEntryType($this->resolvedEntries[$name]);
}
throw new NotFoundException("No entry or class found for '{$name}'");
}
/**
* Get formatted entry type.
*/
private function getEntryType(mixed $entry) : string
{
if (\is_object($entry)) {
return \sprintf("Object (\n class = %s\n)", $entry::class);
}
if (\is_array($entry)) {
return \preg_replace(['/^array \\(/', '/\\)$/'], ['[', ']'], \var_export($entry, \true));
}
if (\is_string($entry)) {
return \sprintf('Value (\'%s\')', $entry);
}
if (\is_bool($entry)) {
return \sprintf('Value (%s)', $entry === \true ? 'true' : 'false');
}
return \sprintf('Value (%s)', \is_scalar($entry) ? (string) $entry : \ucfirst(\gettype($entry)));
}
/**
* Resolves a definition.
*
* Checks for circular dependencies while resolving the definition.
*
* @throws DependencyException Error while resolving the entry.
*/
private function resolveDefinition(Definition $definition, array $parameters = []) : mixed
{
$entryName = $definition->getName();
// Check if we are already getting this entry -> circular dependency
if (isset($this->entriesBeingResolved[$entryName])) {
$entryList = \implode(' -> ', [...\array_keys($this->entriesBeingResolved), $entryName]);
throw new DependencyException("Circular dependency detected while trying to resolve entry '{$entryName}': Dependencies: " . $entryList);
}
$this->entriesBeingResolved[$entryName] = \true;
// Resolve the definition
try {
$value = $this->definitionResolver->resolve($definition, $parameters);
} finally {
unset($this->entriesBeingResolved[$entryName]);
}
return $value;
}
protected function setDefinition(string $name, Definition $definition) : void
{
// Clear existing entry if it exists
if (\array_key_exists($name, $this->resolvedEntries)) {
unset($this->resolvedEntries[$name]);
}
$this->fetchedDefinitions = [];
// Completely clear this local cache
$this->definitionSource->addDefinition($definition);
}
private function getInvoker() : InvokerInterface
{
if (!$this->invoker) {
$parameterResolver = new ResolverChain([new DefinitionParameterResolver($this->definitionResolver), new NumericArrayResolver(), new AssociativeArrayResolver(), new DefaultValueResolver(), new TypeHintContainerResolver($this->delegateContainer)]);
$this->invoker = new Invoker($parameterResolver, $this);
}
return $this->invoker;
}
private function createDefaultDefinitionSource(array $definitions) : SourceChain
{
$autowiring = new ReflectionBasedAutowiring();
$source = new SourceChain([$autowiring]);
$source->setMutableDefinitionSource(new DefinitionArray($definitions, $autowiring));
return $source;
}
}

View File

@@ -1,276 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI;
use PerformanceBooster\DI\Compiler\Compiler;
use PerformanceBooster\DI\Definition\Source\AttributeBasedAutowiring;
use PerformanceBooster\DI\Definition\Source\DefinitionArray;
use PerformanceBooster\DI\Definition\Source\DefinitionFile;
use PerformanceBooster\DI\Definition\Source\DefinitionSource;
use PerformanceBooster\DI\Definition\Source\NoAutowiring;
use PerformanceBooster\DI\Definition\Source\ReflectionBasedAutowiring;
use PerformanceBooster\DI\Definition\Source\SourceCache;
use PerformanceBooster\DI\Definition\Source\SourceChain;
use PerformanceBooster\DI\Proxy\NativeProxyFactory;
use PerformanceBooster\DI\Proxy\ProxyFactory;
use InvalidArgumentException;
use Psr\Container\ContainerInterface;
/**
* Helper to create and configure a Container.
*
* With the default options, the container created is appropriate for the development environment.
*
* Example:
*
* $builder = new ContainerBuilder();
* $container = $builder->build();
*
* @api
*
* @since 3.2
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*
* @psalm-template ContainerClass of Container
*/
class ContainerBuilder
{
/**
* Name of the container class, used to create the container.
* @var class-string<Container>
* @psalm-var class-string<ContainerClass>
*/
private string $containerClass;
/**
* Name of the container parent class, used on compiled container.
* @var class-string<Container>
* @psalm-var class-string<ContainerClass>
*/
private string $containerParentClass;
private bool $useAutowiring = \true;
private bool $useAttributes = \false;
/**
* If set, write the proxies to disk in this directory to improve performances.
*/
private ?string $proxyDirectory = null;
/**
* If PHP-DI is wrapped in another container, this references the wrapper.
*/
private ?ContainerInterface $wrapperContainer = null;
/**
* @var DefinitionSource[]|string[]|array[]
*/
private array $definitionSources = [];
/**
* Whether the container has already been built.
*/
private bool $locked = \false;
private ?string $compileToDirectory = null;
private bool $sourceCache = \false;
protected string $sourceCacheNamespace = '';
/**
* @param class-string<Container> $containerClass Name of the container class, used to create the container.
* @psalm-param class-string<ContainerClass> $containerClass
*/
public function __construct(string $containerClass = Container::class)
{
$this->containerClass = $containerClass;
}
/**
* Build and return a container.
*
* @return Container
* @psalm-return ContainerClass
*/
public function build()
{
$sources = \array_reverse($this->definitionSources);
if ($this->useAttributes) {
$autowiring = new AttributeBasedAutowiring();
$sources[] = $autowiring;
} elseif ($this->useAutowiring) {
$autowiring = new ReflectionBasedAutowiring();
$sources[] = $autowiring;
} else {
$autowiring = new NoAutowiring();
}
$sources = \array_map(function ($definitions) use($autowiring) {
if (\is_string($definitions)) {
// File
return new DefinitionFile($definitions, $autowiring);
}
if (\is_array($definitions)) {
return new DefinitionArray($definitions, $autowiring);
}
return $definitions;
}, $sources);
$source = new SourceChain($sources);
// Mutable definition source
$source->setMutableDefinitionSource(new DefinitionArray([], $autowiring));
if ($this->sourceCache) {
if (!SourceCache::isSupported()) {
throw new \Exception('APCu is not enabled, PHP-DI cannot use it as a cache');
}
// Wrap the source with the cache decorator
$source = new SourceCache($source, $this->sourceCacheNamespace);
}
$proxyFactory = \PHP_VERSION_ID >= 80400 ? new NativeProxyFactory() : new ProxyFactory($this->proxyDirectory);
$this->locked = \true;
$containerClass = $this->containerClass;
if ($this->compileToDirectory) {
$compiler = new Compiler($proxyFactory);
$compiledContainerFile = $compiler->compile($source, $this->compileToDirectory, $containerClass, $this->containerParentClass, $this->useAutowiring);
// Only load the file if it hasn't been already loaded
// (the container can be created multiple times in the same process)
if (!\class_exists($containerClass, \false)) {
require $compiledContainerFile;
}
}
return new $containerClass($source, $proxyFactory, $this->wrapperContainer);
}
/**
* Compile the container for optimum performances.
*
* Be aware that the container is compiled once and never updated!
*
* Therefore:
*
* - in production you should clear that directory every time you deploy
* - in development you should not compile the container
*
* @see https://php-di.org/doc/performances.html
*
* @psalm-template T of CompiledContainer
*
* @param string $directory Directory in which to put the compiled container.
* @param string $containerClass Name of the compiled class. Customize only if necessary.
* @param class-string<Container> $containerParentClass Name of the compiled container parent class. Customize only if necessary.
* @psalm-param class-string<T> $containerParentClass
*
* @psalm-return self<T>
*/
public function enableCompilation(string $directory, string $containerClass = 'CompiledContainer', string $containerParentClass = CompiledContainer::class) : self
{
$this->ensureNotLocked();
$this->compileToDirectory = $directory;
$this->containerClass = $containerClass;
$this->containerParentClass = $containerParentClass;
return $this;
}
/**
* Enable or disable the use of autowiring to guess injections.
*
* Enabled by default.
*
* @return $this
*/
public function useAutowiring(bool $bool) : self
{
$this->ensureNotLocked();
$this->useAutowiring = $bool;
return $this;
}
/**
* Enable or disable the use of PHP 8 attributes to configure injections.
*
* Disabled by default.
*
* @return $this
*/
public function useAttributes(bool $bool) : self
{
$this->ensureNotLocked();
$this->useAttributes = $bool;
return $this;
}
/**
* Configure the proxy generation.
*
* For dev environment, use `writeProxiesToFile(false)` (default configuration)
* For production environment, use `writeProxiesToFile(true, 'tmp/proxies')`
*
* @see https://php-di.org/doc/lazy-injection.html
*
* @param bool $writeToFile If true, write the proxies to disk to improve performances
* @param string|null $proxyDirectory Directory where to write the proxies
* @return $this
* @throws InvalidArgumentException when writeToFile is set to true and the proxy directory is null
*/
public function writeProxiesToFile(bool $writeToFile, ?string $proxyDirectory = null) : self
{
$this->ensureNotLocked();
if ($writeToFile && $proxyDirectory === null) {
throw new InvalidArgumentException('The proxy directory must be specified if you want to write proxies on disk');
}
$this->proxyDirectory = $writeToFile ? $proxyDirectory : null;
return $this;
}
/**
* If PHP-DI's container is wrapped by another container, we can
* set this so that PHP-DI will use the wrapper rather than itself for building objects.
*
* @return $this
*/
public function wrapContainer(ContainerInterface $otherContainer) : self
{
$this->ensureNotLocked();
$this->wrapperContainer = $otherContainer;
return $this;
}
/**
* Add definitions to the container.
*
* @param string|array|DefinitionSource ...$definitions Can be an array of definitions, the
* name of a file containing definitions
* or a DefinitionSource object.
* @return $this
*/
public function addDefinitions(string|array|DefinitionSource ...$definitions) : self
{
$this->ensureNotLocked();
foreach ($definitions as $definition) {
$this->definitionSources[] = $definition;
}
return $this;
}
/**
* Enables the use of APCu to cache definitions.
*
* You must have APCu enabled to use it.
*
* Before using this feature, you should try these steps first:
* - enable compilation if not already done (see `enableCompilation()`)
* - if you use autowiring or attributes, add all the classes you are using into your configuration so that
* PHP-DI knows about them and compiles them
* Once this is done, you can try to optimize performances further with APCu. It can also be useful if you use
* `Container::make()` instead of `get()` (`make()` calls cannot be compiled so they are not optimized).
*
* Remember to clear APCu on each deploy else your application will have a stale cache. Do not enable the cache
* in development environment: any change you will make to the code will be ignored because of the cache.
*
* @see https://php-di.org/doc/performances.html
*
* @param string $cacheNamespace use unique namespace per container when sharing a single APC memory pool to prevent cache collisions
* @return $this
*/
public function enableDefinitionCache(string $cacheNamespace = '') : self
{
$this->ensureNotLocked();
$this->sourceCache = \true;
$this->sourceCacheNamespace = $cacheNamespace;
return $this;
}
/**
* Are we building a compiled container?
*/
public function isCompilationEnabled() : bool
{
return (bool) $this->compileToDirectory;
}
private function ensureNotLocked() : void
{
if ($this->locked) {
throw new \LogicException('The ContainerBuilder cannot be modified after the container has been built');
}
}
}

View File

@@ -1,52 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Definition;
/**
* Definition of an array containing values or references.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ArrayDefinition implements Definition
{
/** Entry name. */
private string $name = '';
public function __construct(private array $values)
{
}
public function getName() : string
{
return $this->name;
}
public function setName(string $name) : void
{
$this->name = $name;
}
public function getValues() : array
{
return $this->values;
}
public function replaceNestedDefinitions(callable $replacer) : void
{
$this->values = \array_map($replacer, $this->values);
}
public function __toString() : string
{
$str = '[' . \PHP_EOL;
foreach ($this->values as $key => $value) {
if (\is_string($key)) {
$key = "'" . $key . "'";
}
$str .= ' ' . $key . ' => ';
if ($value instanceof Definition) {
$str .= \str_replace(\PHP_EOL, \PHP_EOL . ' ', (string) $value);
} else {
$str .= \var_export($value, \true);
}
$str .= ',' . \PHP_EOL;
}
return $str . ']';
}
}

View File

@@ -1,30 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Definition;
use PerformanceBooster\DI\Definition\Exception\InvalidDefinition;
/**
* Extends an array definition by adding new elements into it.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ArrayDefinitionExtension extends ArrayDefinition implements ExtendsPreviousDefinition
{
private ?ArrayDefinition $subDefinition = null;
public function getValues() : array
{
if (!$this->subDefinition) {
return parent::getValues();
}
return \array_merge($this->subDefinition->getValues(), parent::getValues());
}
public function setExtendedDefinition(Definition $definition) : void
{
if (!$definition instanceof ArrayDefinition) {
throw new InvalidDefinition(\sprintf('Definition %s tries to add array entries but the previous definition is not an array', $this->getName()));
}
$this->subDefinition = $definition;
}
}

View File

@@ -1,11 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Definition;
/**
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class AutowireDefinition extends ObjectDefinition
{
}

View File

@@ -1,31 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Definition;
/**
* Factory that decorates a sub-definition.
*
* @since 5.0
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class DecoratorDefinition extends FactoryDefinition implements Definition, ExtendsPreviousDefinition
{
private ?Definition $decorated = null;
public function setExtendedDefinition(Definition $definition) : void
{
$this->decorated = $definition;
}
public function getDecoratedDefinition() : ?Definition
{
return $this->decorated;
}
public function replaceNestedDefinitions(callable $replacer) : void
{
// no nested definitions
}
public function __toString() : string
{
return 'Decorate(' . $this->getName() . ')';
}
}

View File

@@ -1,32 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Definition;
use PerformanceBooster\DI\Factory\RequestedEntry;
/**
* Definition.
*
* @internal This interface is internal to PHP-DI and may change between minor versions.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface Definition extends RequestedEntry, \Stringable
{
/**
* Returns the name of the entry in the container.
*/
public function getName() : string;
/**
* Set the name of the entry in the container.
*/
public function setName(string $name) : void;
/**
* Apply a callable that replaces the definitions nested in this definition.
*/
public function replaceNestedDefinitions(callable $replacer) : void;
/**
* Definitions can be cast to string for debugging information.
*/
public function __toString() : string;
}

View File

@@ -1,109 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Definition\Dumper;
use PerformanceBooster\DI\Definition\Definition;
use PerformanceBooster\DI\Definition\ObjectDefinition;
use PerformanceBooster\DI\Definition\ObjectDefinition\MethodInjection;
use ReflectionException;
/**
* Dumps object definitions to string for debugging purposes.
*
* @since 4.1
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class ObjectDefinitionDumper
{
/**
* Returns the definition as string representation.
*/
public function dump(ObjectDefinition $definition) : string
{
$className = $definition->getClassName();
$classExist = \class_exists($className) || \interface_exists($className);
// Class
if (!$classExist) {
$warning = '#UNKNOWN# ';
} else {
$class = new \ReflectionClass($className);
$warning = $class->isInstantiable() ? '' : '#NOT INSTANTIABLE# ';
}
$str = \sprintf(' class = %s%s', $warning, $className);
// Lazy
$str .= \PHP_EOL . ' lazy = ' . \var_export($definition->isLazy(), \true);
if ($classExist) {
// Constructor
$str .= $this->dumpConstructor($className, $definition);
// Properties
$str .= $this->dumpProperties($definition);
// Methods
$str .= $this->dumpMethods($className, $definition);
}
return \sprintf('Object (' . \PHP_EOL . '%s' . \PHP_EOL . ')', $str);
}
/**
* @param class-string $className
*/
private function dumpConstructor(string $className, ObjectDefinition $definition) : string
{
$str = '';
$constructorInjection = $definition->getConstructorInjection();
if ($constructorInjection !== null) {
$parameters = $this->dumpMethodParameters($className, $constructorInjection);
$str .= \sprintf(\PHP_EOL . ' __construct(' . \PHP_EOL . ' %s' . \PHP_EOL . ' )', $parameters);
}
return $str;
}
private function dumpProperties(ObjectDefinition $definition) : string
{
$str = '';
foreach ($definition->getPropertyInjections() as $propertyInjection) {
$value = $propertyInjection->getValue();
$valueStr = $value instanceof Definition ? (string) $value : \var_export($value, \true);
$str .= \sprintf(\PHP_EOL . ' $%s = %s', $propertyInjection->getPropertyName(), $valueStr);
}
return $str;
}
/**
* @param class-string $className
*/
private function dumpMethods(string $className, ObjectDefinition $definition) : string
{
$str = '';
foreach ($definition->getMethodInjections() as $methodInjection) {
$parameters = $this->dumpMethodParameters($className, $methodInjection);
$str .= \sprintf(\PHP_EOL . ' %s(' . \PHP_EOL . ' %s' . \PHP_EOL . ' )', $methodInjection->getMethodName(), $parameters);
}
return $str;
}
/**
* @param class-string $className
*/
private function dumpMethodParameters(string $className, MethodInjection $methodInjection) : string
{
$methodReflection = new \ReflectionMethod($className, $methodInjection->getMethodName());
$args = [];
$definitionParameters = $methodInjection->getParameters();
foreach ($methodReflection->getParameters() as $index => $parameter) {
if (\array_key_exists($index, $definitionParameters)) {
$value = $definitionParameters[$index];
$valueStr = $value instanceof Definition ? (string) $value : \var_export($value, \true);
$args[] = \sprintf('$%s = %s', $parameter->getName(), $valueStr);
continue;
}
// If the parameter is optional and wasn't specified, we take its default value
if ($parameter->isOptional()) {
try {
$value = $parameter->getDefaultValue();
$args[] = \sprintf('$%s = (default value) %s', $parameter->getName(), \var_export($value, \true));
continue;
} catch (ReflectionException) {
// The default value can't be read through Reflection because it is a PHP internal class
}
}
$args[] = \sprintf('$%s = #UNDEFINED#', $parameter->getName());
}
return \implode(\PHP_EOL . ' ', $args);
}
}

View File

@@ -1,71 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Definition;
/**
* Defines a reference to an environment variable, with fallback to a default
* value if the environment variable is not defined.
*
* @author James Harris <james.harris@icecave.com.au>
*/
class EnvironmentVariableDefinition implements Definition
{
/** Entry name. */
private string $name = '';
/**
* @param string $variableName The name of the environment variable
* @param bool $isOptional Whether or not the environment variable definition is optional. If true and the environment variable given by $variableName has not been defined, $defaultValue is used.
* @param mixed $defaultValue The default value to use if the environment variable is optional and not provided
*/
public function __construct(private string $variableName, private bool $isOptional = \false, private mixed $defaultValue = null)
{
}
public function getName() : string
{
return $this->name;
}
public function setName(string $name) : void
{
$this->name = $name;
}
/**
* @return string The name of the environment variable
*/
public function getVariableName() : string
{
return $this->variableName;
}
/**
* @return bool Whether or not the environment variable definition is optional
*/
public function isOptional() : bool
{
return $this->isOptional;
}
/**
* @return mixed The default value to use if the environment variable is optional and not provided
*/
public function getDefaultValue() : mixed
{
return $this->defaultValue;
}
public function replaceNestedDefinitions(callable $replacer) : void
{
$this->defaultValue = $replacer($this->defaultValue);
}
public function __toString() : string
{
$str = ' variable = ' . $this->variableName . \PHP_EOL . ' optional = ' . ($this->isOptional ? 'yes' : 'no');
if ($this->isOptional) {
if ($this->defaultValue instanceof Definition) {
$nestedDefinition = (string) $this->defaultValue;
$defaultValueStr = \str_replace(\PHP_EOL, \PHP_EOL . ' ', $nestedDefinition);
} else {
$defaultValueStr = \var_export($this->defaultValue, \true);
}
$str .= \PHP_EOL . ' default = ' . $defaultValueStr;
}
return \sprintf('Environment variable (' . \PHP_EOL . '%s' . \PHP_EOL . ')', $str);
}
}

View File

@@ -1,13 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Definition\Exception;
/**
* Error in the definitions using PHP attributes.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class InvalidAttribute extends InvalidDefinition
{
}

View File

@@ -1,19 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Definition\Exception;
use PerformanceBooster\DI\Definition\Definition;
use Psr\Container\ContainerExceptionInterface;
/**
* Invalid DI definitions.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class InvalidDefinition extends \Exception implements ContainerExceptionInterface
{
public static function create(Definition $definition, string $message, ?\Exception $previous = null) : self
{
return new self(\sprintf('%s' . \PHP_EOL . 'Full definition:' . \PHP_EOL . '%s', $message, (string) $definition), 0, $previous);
}
}

View File

@@ -1,14 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Definition;
/**
* A definition that extends a previous definition with the same name.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface ExtendsPreviousDefinition extends Definition
{
public function setExtendedDefinition(Definition $definition) : void;
}

View File

@@ -1,68 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Definition;
/**
* Definition of a value or class with a factory.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class FactoryDefinition implements Definition
{
/**
* Entry name.
*/
private string $name;
/**
* Callable that returns the value.
* @var callable
*/
private $factory;
/**
* Factory parameters.
* @var mixed[]
*/
private array $parameters;
/**
* @param string $name Entry name
* @param callable|array|string $factory Callable that returns the value associated to the entry name.
* @param array $parameters Parameters to be passed to the callable
*/
public function __construct(string $name, callable|array|string $factory, array $parameters = [])
{
$this->name = $name;
$this->factory = $factory;
$this->parameters = $parameters;
}
public function getName() : string
{
return $this->name;
}
public function setName(string $name) : void
{
$this->name = $name;
}
/**
* @return callable|array|string Callable that returns the value associated to the entry name.
*/
public function getCallable() : callable|array|string
{
return $this->factory;
}
/**
* @return array Array containing the parameters to be passed to the callable, indexed by name.
*/
public function getParameters() : array
{
return $this->parameters;
}
public function replaceNestedDefinitions(callable $replacer) : void
{
$this->parameters = \array_map($replacer, $this->parameters);
}
public function __toString() : string
{
return 'Factory';
}
}

View File

@@ -1,63 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Definition\Helper;
use PerformanceBooster\DI\Definition\AutowireDefinition;
/**
* Helps defining how to create an instance of a class using autowiring.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class AutowireDefinitionHelper extends CreateDefinitionHelper
{
public const DEFINITION_CLASS = AutowireDefinition::class;
/**
* Defines a value for a specific argument of the constructor.
*
* This method is usually used together with attributes or autowiring, when a parameter
* is not (or cannot be) type-hinted. Using this method instead of constructor() allows to
* avoid defining all the parameters (letting them being resolved using attributes or autowiring)
* and only define one.
*
* @param string|int $parameter Parameter name of position for which the value will be given.
* @param mixed $value Value to give to this parameter.
*
* @return $this
*/
public function constructorParameter(string|int $parameter, mixed $value) : self
{
$this->constructor[$parameter] = $value;
return $this;
}
/**
* Defines a method to call and a value for a specific argument.
*
* This method is usually used together with attributes or autowiring, when a parameter
* is not (or cannot be) type-hinted. Using this method instead of method() allows to
* avoid defining all the parameters (letting them being resolved using attributes or
* autowiring) and only define one.
*
* If multiple calls to the method have been configured already (e.g. in a previous definition)
* then this method only overrides the parameter for the *first* call.
*
* @param string $method Name of the method to call.
* @param string|int $parameter Parameter name of position for which the value will be given.
* @param mixed $value Value to give to this parameter.
*
* @return $this
*/
public function methodParameter(string $method, string|int $parameter, mixed $value) : self
{
// Special case for the constructor
if ($method === '__construct') {
$this->constructor[$parameter] = $value;
return $this;
}
if (!isset($this->methods[$method])) {
$this->methods[$method] = [0 => []];
}
$this->methods[$method][0][$parameter] = $value;
return $this;
}
}

View File

@@ -1,158 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Definition\Helper;
use PerformanceBooster\DI\Definition\Exception\InvalidDefinition;
use PerformanceBooster\DI\Definition\ObjectDefinition;
use PerformanceBooster\DI\Definition\ObjectDefinition\MethodInjection;
use PerformanceBooster\DI\Definition\ObjectDefinition\PropertyInjection;
/**
* Helps defining how to create an instance of a class.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class CreateDefinitionHelper implements DefinitionHelper
{
private const DEFINITION_CLASS = ObjectDefinition::class;
private ?string $className;
private ?bool $lazy = null;
/**
* Array of constructor parameters.
*/
protected array $constructor = [];
/**
* Array of properties and their value.
*/
private array $properties = [];
/**
* Array of methods and their parameters.
*/
protected array $methods = [];
/**
* Helper for defining an object.
*
* @param string|null $className Class name of the object.
* If null, the name of the entry (in the container) will be used as class name.
*/
public function __construct(?string $className = null)
{
$this->className = $className;
}
/**
* Define the entry as lazy.
*
* A lazy entry is created only when it is used, a proxy is injected instead.
*
* @return $this
*/
public function lazy() : self
{
$this->lazy = \true;
return $this;
}
/**
* Defines the arguments to use to call the constructor.
*
* This method takes a variable number of arguments, example:
* ->constructor($param1, $param2, $param3)
*
* @param mixed ...$parameters Parameters to use for calling the constructor of the class.
*
* @return $this
*/
public function constructor(mixed ...$parameters) : self
{
$this->constructor = $parameters;
return $this;
}
/**
* Defines a value to inject in a property of the object.
*
* @param string $property Entry in which to inject the value.
* @param mixed $value Value to inject in the property.
*
* @return $this
*/
public function property(string $property, mixed $value) : self
{
$this->properties[$property] = $value;
return $this;
}
/**
* Defines a method to call and the arguments to use.
*
* This method takes a variable number of arguments after the method name, example:
*
* ->method('myMethod', $param1, $param2)
*
* Can be used multiple times to declare multiple calls.
*
* @param string $method Name of the method to call.
* @param mixed ...$parameters Parameters to use for calling the method.
*
* @return $this
*/
public function method(string $method, mixed ...$parameters) : self
{
if (!isset($this->methods[$method])) {
$this->methods[$method] = [];
}
$this->methods[$method][] = $parameters;
return $this;
}
public function getDefinition(string $entryName) : ObjectDefinition
{
$class = $this::DEFINITION_CLASS;
/** @var ObjectDefinition $definition */
$definition = new $class($entryName, $this->className);
if ($this->lazy !== null) {
$definition->setLazy($this->lazy);
}
if (!empty($this->constructor)) {
$parameters = $this->fixParameters($definition, '__construct', $this->constructor);
$constructorInjection = MethodInjection::constructor($parameters);
$definition->setConstructorInjection($constructorInjection);
}
if (!empty($this->properties)) {
foreach ($this->properties as $property => $value) {
$definition->addPropertyInjection(new PropertyInjection($property, $value));
}
}
if (!empty($this->methods)) {
foreach ($this->methods as $method => $calls) {
foreach ($calls as $parameters) {
$parameters = $this->fixParameters($definition, $method, $parameters);
$methodInjection = new MethodInjection($method, $parameters);
$definition->addMethodInjection($methodInjection);
}
}
}
return $definition;
}
/**
* Fixes parameters indexed by the parameter name -> reindex by position.
*
* This is necessary so that merging definitions between sources is possible.
*
* @throws InvalidDefinition
*/
private function fixParameters(ObjectDefinition $definition, string $method, array $parameters) : array
{
$fixedParameters = [];
foreach ($parameters as $index => $parameter) {
// Parameter indexed by the parameter name, we reindex it with its position
if (\is_string($index)) {
$callable = [$definition->getClassName(), $method];
try {
$reflectionParameter = new \ReflectionParameter($callable, $index);
} catch (\ReflectionException $e) {
throw InvalidDefinition::create($definition, \sprintf("Parameter with name '%s' could not be found. %s.", $index, $e->getMessage()));
}
$index = $reflectionParameter->getPosition();
}
$fixedParameters[$index] = $parameter;
}
return $fixedParameters;
}
}

View File

@@ -1,18 +0,0 @@
<?php
declare (strict_types=1);
namespace PerformanceBooster\DI\Definition\Helper;
use PerformanceBooster\DI\Definition\Definition;
/**
* Helps defining container entries.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
interface DefinitionHelper
{
/**
* @param string $entryName Container entry name
*/
public function getDefinition(string $entryName) : Definition;
}

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