|
After Width: | Height: | Size: 38 KiB |
@ -0,0 +1,2 @@ |
|||
|
|||
!.gitignore |
|||
@ -0,0 +1,7 @@ |
|||
<?php |
|||
|
|||
// autoload.php @generated by Composer |
|||
|
|||
require_once __DIR__ . '/composer/autoload_real.php'; |
|||
|
|||
return ComposerAutoloaderInit9caf0e5e183c101daf530f1044c09467::getLoader(); |
|||
@ -0,0 +1,14 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
dir=$(cd "${0%[/\\]*}" > /dev/null; cd "../zircote/swagger-php/bin" && pwd) |
|||
|
|||
if [ -d /proc/cygdrive ]; then |
|||
case $(which php) in |
|||
$(readlink -n /proc/cygdrive)/*) |
|||
# We are in Cygwin using Windows php, so the path must be translated |
|||
dir=$(cygpath -m "$dir"); |
|||
;; |
|||
esac |
|||
fi |
|||
|
|||
"${dir}/openapi" "$@" |
|||
@ -0,0 +1,4 @@ |
|||
@ECHO OFF |
|||
setlocal DISABLEDELAYEDEXPANSION |
|||
SET BIN_TARGET=%~dp0/../zircote/swagger-php/bin/openapi |
|||
php "%BIN_TARGET%" %* |
|||
@ -0,0 +1,24 @@ |
|||
#!/usr/bin/env php |
|||
<?php |
|||
|
|||
/** |
|||
* Proxy PHP file generated by Composer |
|||
* |
|||
* This file includes the referenced bin path (../symfony/var-dumper/Resources/bin/var-dump-server) using eval to remove the shebang if present |
|||
* |
|||
* @generated |
|||
*/ |
|||
|
|||
$binPath = realpath(__DIR__ . "/" . '../symfony/var-dumper/Resources/bin/var-dump-server'); |
|||
$contents = file_get_contents($binPath); |
|||
$contents = preg_replace('{^#!/.+\r?\n<\?(php)?}', '', $contents, 1, $replaced); |
|||
if ($replaced) { |
|||
$contents = strtr($contents, array( |
|||
'__FILE__' => var_export($binPath, true), |
|||
'__DIR__' => var_export(dirname($binPath), true), |
|||
)); |
|||
|
|||
eval($contents); |
|||
exit(0); |
|||
} |
|||
include $binPath; |
|||
@ -0,0 +1,4 @@ |
|||
@ECHO OFF |
|||
setlocal DISABLEDELAYEDEXPANSION |
|||
SET BIN_TARGET=%~dp0/../symfony/var-dumper/Resources/bin/var-dump-server |
|||
php "%BIN_TARGET%" %* |
|||
@ -0,0 +1,24 @@ |
|||
#!/usr/bin/env php |
|||
<?php |
|||
|
|||
/** |
|||
* Proxy PHP file generated by Composer |
|||
* |
|||
* This file includes the referenced bin path (../symfony/yaml/Resources/bin/yaml-lint) using eval to remove the shebang if present |
|||
* |
|||
* @generated |
|||
*/ |
|||
|
|||
$binPath = realpath(__DIR__ . "/" . '../symfony/yaml/Resources/bin/yaml-lint'); |
|||
$contents = file_get_contents($binPath); |
|||
$contents = preg_replace('{^#!/.+\r?\n<\?(php)?}', '', $contents, 1, $replaced); |
|||
if ($replaced) { |
|||
$contents = strtr($contents, array( |
|||
'__FILE__' => var_export($binPath, true), |
|||
'__DIR__' => var_export(dirname($binPath), true), |
|||
)); |
|||
|
|||
eval($contents); |
|||
exit(0); |
|||
} |
|||
include $binPath; |
|||
@ -0,0 +1,4 @@ |
|||
@ECHO OFF |
|||
setlocal DISABLEDELAYEDEXPANSION |
|||
SET BIN_TARGET=%~dp0/../symfony/yaml/Resources/bin/yaml-lint |
|||
php "%BIN_TARGET%" %* |
|||
@ -0,0 +1,479 @@ |
|||
<?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 |
|||
{ |
|||
private $vendorDir; |
|||
|
|||
// PSR-4 |
|||
private $prefixLengthsPsr4 = array(); |
|||
private $prefixDirsPsr4 = array(); |
|||
private $fallbackDirsPsr4 = array(); |
|||
|
|||
// PSR-0 |
|||
private $prefixesPsr0 = array(); |
|||
private $fallbackDirsPsr0 = array(); |
|||
|
|||
private $useIncludePath = false; |
|||
private $classMap = array(); |
|||
private $classMapAuthoritative = false; |
|||
private $missingClasses = array(); |
|||
private $apcuPrefix; |
|||
|
|||
private static $registeredLoaders = array(); |
|||
|
|||
public function __construct($vendorDir = null) |
|||
{ |
|||
$this->vendorDir = $vendorDir; |
|||
} |
|||
|
|||
public function getPrefixes() |
|||
{ |
|||
if (!empty($this->prefixesPsr0)) { |
|||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); |
|||
} |
|||
|
|||
return array(); |
|||
} |
|||
|
|||
public function getPrefixesPsr4() |
|||
{ |
|||
return $this->prefixDirsPsr4; |
|||
} |
|||
|
|||
public function getFallbackDirs() |
|||
{ |
|||
return $this->fallbackDirsPsr0; |
|||
} |
|||
|
|||
public function getFallbackDirsPsr4() |
|||
{ |
|||
return $this->fallbackDirsPsr4; |
|||
} |
|||
|
|||
public function getClassMap() |
|||
{ |
|||
return $this->classMap; |
|||
} |
|||
|
|||
/** |
|||
* @param array $classMap Class to filename map |
|||
*/ |
|||
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 array|string $paths The PSR-0 root directories |
|||
* @param bool $prepend Whether to prepend the directories |
|||
*/ |
|||
public function add($prefix, $paths, $prepend = false) |
|||
{ |
|||
if (!$prefix) { |
|||
if ($prepend) { |
|||
$this->fallbackDirsPsr0 = array_merge( |
|||
(array) $paths, |
|||
$this->fallbackDirsPsr0 |
|||
); |
|||
} else { |
|||
$this->fallbackDirsPsr0 = array_merge( |
|||
$this->fallbackDirsPsr0, |
|||
(array) $paths |
|||
); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
$first = $prefix[0]; |
|||
if (!isset($this->prefixesPsr0[$first][$prefix])) { |
|||
$this->prefixesPsr0[$first][$prefix] = (array) $paths; |
|||
|
|||
return; |
|||
} |
|||
if ($prepend) { |
|||
$this->prefixesPsr0[$first][$prefix] = array_merge( |
|||
(array) $paths, |
|||
$this->prefixesPsr0[$first][$prefix] |
|||
); |
|||
} else { |
|||
$this->prefixesPsr0[$first][$prefix] = array_merge( |
|||
$this->prefixesPsr0[$first][$prefix], |
|||
(array) $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 array|string $paths The PSR-4 base directories |
|||
* @param bool $prepend Whether to prepend the directories |
|||
* |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
public function addPsr4($prefix, $paths, $prepend = false) |
|||
{ |
|||
if (!$prefix) { |
|||
// Register directories for the root namespace. |
|||
if ($prepend) { |
|||
$this->fallbackDirsPsr4 = array_merge( |
|||
(array) $paths, |
|||
$this->fallbackDirsPsr4 |
|||
); |
|||
} else { |
|||
$this->fallbackDirsPsr4 = array_merge( |
|||
$this->fallbackDirsPsr4, |
|||
(array) $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] = (array) $paths; |
|||
} elseif ($prepend) { |
|||
// Prepend directories for an already registered namespace. |
|||
$this->prefixDirsPsr4[$prefix] = array_merge( |
|||
(array) $paths, |
|||
$this->prefixDirsPsr4[$prefix] |
|||
); |
|||
} else { |
|||
// Append directories for an already registered namespace. |
|||
$this->prefixDirsPsr4[$prefix] = array_merge( |
|||
$this->prefixDirsPsr4[$prefix], |
|||
(array) $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 array|string $paths The PSR-0 base directories |
|||
*/ |
|||
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 array|string $paths The PSR-4 base directories |
|||
* |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
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 |
|||
*/ |
|||
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 |
|||
*/ |
|||
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 |
|||
*/ |
|||
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 |
|||
*/ |
|||
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. |
|||
*/ |
|||
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 bool|null True if loaded, null otherwise |
|||
*/ |
|||
public function loadClass($class) |
|||
{ |
|||
if ($file = $this->findFile($class)) { |
|||
includeFile($file); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 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 indexed by their corresponding vendor directories. |
|||
* |
|||
* @return self[] |
|||
*/ |
|||
public static function getRegisteredLoaders() |
|||
{ |
|||
return self::$registeredLoaders; |
|||
} |
|||
|
|||
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; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Scope isolated include. |
|||
* |
|||
* Prevents access to $this/self from included files. |
|||
*/ |
|||
function includeFile($file) |
|||
{ |
|||
include $file; |
|||
} |
|||
@ -0,0 +1,571 @@ |
|||
<?php |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
namespace Composer; |
|||
|
|||
use Composer\Autoload\ClassLoader; |
|||
use Composer\Semver\VersionParser; |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
class InstalledVersions |
|||
{ |
|||
private static $installed = array ( |
|||
'root' => |
|||
array ( |
|||
'pretty_version' => 'dev-master', |
|||
'version' => 'dev-master', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'e60cfe2b6616b1bcb1c91f148a81369e11161013', |
|||
'name' => 'topthink/think', |
|||
), |
|||
'versions' => |
|||
array ( |
|||
'doctrine/annotations' => |
|||
array ( |
|||
'pretty_version' => '1.12.1', |
|||
'version' => '1.12.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'b17c5014ef81d212ac539f07a1001832df1b6d3b', |
|||
), |
|||
'doctrine/lexer' => |
|||
array ( |
|||
'pretty_version' => '1.2.1', |
|||
'version' => '1.2.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'e864bbf5904cb8f5bb334f99209b48018522f042', |
|||
), |
|||
'edward1108/edward-captcha' => |
|||
array ( |
|||
'pretty_version' => '1.1', |
|||
'version' => '1.1.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '426ceee34507c30d4b21e0dd349b571371aef700', |
|||
), |
|||
'egulias/email-validator' => |
|||
array ( |
|||
'pretty_version' => '2.1.25', |
|||
'version' => '2.1.25.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '0dbf5d78455d4d6a41d186da50adc1122ec066f4', |
|||
), |
|||
'lcobucci/clock' => |
|||
array ( |
|||
'pretty_version' => '2.0.0', |
|||
'version' => '2.0.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '353d83fe2e6ae95745b16b3d911813df6a05bfb3', |
|||
), |
|||
'lcobucci/jwt' => |
|||
array ( |
|||
'pretty_version' => '4.1.2', |
|||
'version' => '4.1.2.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'c544710aa18e079baf0027ca4c8236913f46945b', |
|||
), |
|||
'league/flysystem' => |
|||
array ( |
|||
'pretty_version' => '1.1.3', |
|||
'version' => '1.1.3.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '9be3b16c877d477357c015cec057548cf9b2a14a', |
|||
), |
|||
'league/flysystem-cached-adapter' => |
|||
array ( |
|||
'pretty_version' => '1.1.0', |
|||
'version' => '1.1.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'd1925efb2207ac4be3ad0c40b8277175f99ffaff', |
|||
), |
|||
'league/mime-type-detection' => |
|||
array ( |
|||
'pretty_version' => '1.7.0', |
|||
'version' => '1.7.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3', |
|||
), |
|||
'psr/cache' => |
|||
array ( |
|||
'pretty_version' => '1.0.1', |
|||
'version' => '1.0.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8', |
|||
), |
|||
'psr/container' => |
|||
array ( |
|||
'pretty_version' => '1.0.0', |
|||
'version' => '1.0.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'b7ce3b176482dbbc1245ebf52b181af44c2cf55f', |
|||
), |
|||
'psr/log' => |
|||
array ( |
|||
'pretty_version' => '1.1.3', |
|||
'version' => '1.1.3.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '0f73288fd15629204f9d42b7055f72dacbe811fc', |
|||
), |
|||
'psr/simple-cache' => |
|||
array ( |
|||
'pretty_version' => '1.0.1', |
|||
'version' => '1.0.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b', |
|||
), |
|||
'swiftmailer/swiftmailer' => |
|||
array ( |
|||
'pretty_version' => 'v6.2.5', |
|||
'version' => '6.2.5.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '698a6a9f54d7eb321274de3ad19863802c879fb7', |
|||
), |
|||
'symfony/deprecation-contracts' => |
|||
array ( |
|||
'pretty_version' => 'v2.2.0', |
|||
'version' => '2.2.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '5fa56b4074d1ae755beb55617ddafe6f5d78f665', |
|||
), |
|||
'symfony/finder' => |
|||
array ( |
|||
'pretty_version' => 'v5.2.4', |
|||
'version' => '5.2.4.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '0d639a0943822626290d169965804f79400e6a04', |
|||
), |
|||
'symfony/polyfill-ctype' => |
|||
array ( |
|||
'pretty_version' => 'v1.22.1', |
|||
'version' => '1.22.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'c6c942b1ac76c82448322025e084cadc56048b4e', |
|||
), |
|||
'symfony/polyfill-iconv' => |
|||
array ( |
|||
'pretty_version' => 'v1.22.1', |
|||
'version' => '1.22.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '06fb361659649bcfd6a208a0f1fcaf4e827ad342', |
|||
), |
|||
'symfony/polyfill-intl-idn' => |
|||
array ( |
|||
'pretty_version' => 'v1.22.1', |
|||
'version' => '1.22.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '2d63434d922daf7da8dd863e7907e67ee3031483', |
|||
), |
|||
'symfony/polyfill-intl-normalizer' => |
|||
array ( |
|||
'pretty_version' => 'v1.22.1', |
|||
'version' => '1.22.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '43a0283138253ed1d48d352ab6d0bdb3f809f248', |
|||
), |
|||
'symfony/polyfill-mbstring' => |
|||
array ( |
|||
'pretty_version' => 'v1.22.1', |
|||
'version' => '1.22.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '5232de97ee3b75b0360528dae24e73db49566ab1', |
|||
), |
|||
'symfony/polyfill-php72' => |
|||
array ( |
|||
'pretty_version' => 'v1.22.1', |
|||
'version' => '1.22.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9', |
|||
), |
|||
'symfony/polyfill-php80' => |
|||
array ( |
|||
'pretty_version' => 'v1.22.1', |
|||
'version' => '1.22.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'dc3063ba22c2a1fd2f45ed856374d79114998f91', |
|||
), |
|||
'symfony/var-dumper' => |
|||
array ( |
|||
'pretty_version' => 'v4.4.20', |
|||
'version' => '4.4.20.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'a1eab2f69906dc83c5ddba4632180260d0ab4f7f', |
|||
), |
|||
'symfony/yaml' => |
|||
array ( |
|||
'pretty_version' => 'v5.2.4', |
|||
'version' => '5.2.4.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '7d6ae0cce3c33965af681a4355f1c4de326ed277', |
|||
), |
|||
'topthink/framework' => |
|||
array ( |
|||
'pretty_version' => 'v6.0.7', |
|||
'version' => '6.0.7.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'db8fe22520a9660dd5e4c87e304034ac49e39270', |
|||
), |
|||
'topthink/think' => |
|||
array ( |
|||
'pretty_version' => 'dev-master', |
|||
'version' => 'dev-master', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'e60cfe2b6616b1bcb1c91f148a81369e11161013', |
|||
), |
|||
'topthink/think-helper' => |
|||
array ( |
|||
'pretty_version' => 'v3.1.4', |
|||
'version' => '3.1.4.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'c28d37743bda4a0455286ca85b17b5791d626e10', |
|||
), |
|||
'topthink/think-migration' => |
|||
array ( |
|||
'pretty_version' => 'v3.0.3', |
|||
'version' => '3.0.3.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '5717d9e5f3ea745f6dbfd1e30b4402aaadff9a79', |
|||
), |
|||
'topthink/think-multi-app' => |
|||
array ( |
|||
'pretty_version' => 'v1.0.14', |
|||
'version' => '1.0.14.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'ccaad7c2d33f42cb1cc2a78d6610aaec02cea4c3', |
|||
), |
|||
'topthink/think-orm' => |
|||
array ( |
|||
'pretty_version' => 'v2.0.39', |
|||
'version' => '2.0.39.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '39a9d0a0e52d9b8bad9d98484d8484cdf5b683a7', |
|||
), |
|||
'topthink/think-trace' => |
|||
array ( |
|||
'pretty_version' => 'v1.4', |
|||
'version' => '1.4.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '9a9fa8f767b6c66c5a133ad21ca1bc96ad329444', |
|||
), |
|||
'zircote/swagger-php' => |
|||
array ( |
|||
'pretty_version' => '3.1.0', |
|||
'version' => '3.1.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '9d172471e56433b5c7061006b9a766f262a3edfd', |
|||
), |
|||
), |
|||
); |
|||
private static $canGetVendors; |
|||
private static $installedByVendor = array(); |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
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))); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
public static function isInstalled($packageName) |
|||
{ |
|||
foreach (self::getInstalled() as $installed) { |
|||
if (isset($installed['versions'][$packageName])) { |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
public static function satisfies(VersionParser $parser, $packageName, $constraint) |
|||
{ |
|||
$constraint = $parser->parseConstraints($constraint); |
|||
$provided = $parser->parseConstraints(self::getVersionRanges($packageName)); |
|||
|
|||
return $provided->matches($constraint); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
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'); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
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'); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
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'); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
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'); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
public static function getRootPackage() |
|||
{ |
|||
$installed = self::getInstalled(); |
|||
|
|||
return $installed[0]['root']; |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
public static function getRawData() |
|||
{ |
|||
return self::$installed; |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
public static function reload($data) |
|||
{ |
|||
self::$installed = $data; |
|||
self::$installedByVendor = array(); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
private static function getInstalled() |
|||
{ |
|||
if (null === self::$canGetVendors) { |
|||
self::$canGetVendors = method_exists('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')) { |
|||
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; |
|||
} |
|||
} |
|||
} |
|||
|
|||
$installed[] = self::$installed; |
|||
|
|||
return $installed; |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
|
|||
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. |
|||
|
|||
@ -0,0 +1,15 @@ |
|||
<?php |
|||
|
|||
// autoload_classmap.php @generated by Composer |
|||
|
|||
$vendorDir = dirname(dirname(__FILE__)); |
|||
$baseDir = dirname($vendorDir); |
|||
|
|||
return array( |
|||
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', |
|||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', |
|||
'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', |
|||
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', |
|||
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', |
|||
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', |
|||
); |
|||
@ -0,0 +1,23 @@ |
|||
<?php |
|||
|
|||
// autoload_files.php @generated by Composer |
|||
|
|||
$vendorDir = dirname(dirname(__FILE__)); |
|||
$baseDir = dirname($vendorDir); |
|||
|
|||
return array( |
|||
'9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php', |
|||
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', |
|||
'35fab96057f1bf5e7aba31a8a6d5fdde' => $vendorDir . '/topthink/think-orm/stubs/load_stubs.php', |
|||
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', |
|||
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', |
|||
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', |
|||
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', |
|||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', |
|||
'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php', |
|||
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', |
|||
'b2759b3fbd06631840a9638abcd22485' => $vendorDir . '/edward1108/edward-captcha/src/helper.php', |
|||
'2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php', |
|||
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', |
|||
'0ccdf99b8f62f02c52cba55802e0c2e7' => $vendorDir . '/zircote/swagger-php/src/functions.php', |
|||
); |
|||
@ -0,0 +1,10 @@ |
|||
<?php |
|||
|
|||
// autoload_namespaces.php @generated by Composer |
|||
|
|||
$vendorDir = dirname(dirname(__FILE__)); |
|||
$baseDir = dirname($vendorDir); |
|||
|
|||
return array( |
|||
'' => array($baseDir . '/extend'), |
|||
); |
|||
@ -0,0 +1,39 @@ |
|||
<?php |
|||
|
|||
// autoload_psr4.php @generated by Composer |
|||
|
|||
$vendorDir = dirname(dirname(__FILE__)); |
|||
$baseDir = dirname($vendorDir); |
|||
|
|||
return array( |
|||
'think\\trace\\' => array($vendorDir . '/topthink/think-trace/src'), |
|||
'think\\migration\\' => array($vendorDir . '/topthink/think-migration/src'), |
|||
'think\\app\\' => array($vendorDir . '/topthink/think-multi-app/src'), |
|||
'think\\' => array($vendorDir . '/topthink/framework/src/think', $vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src'), |
|||
'edward\\captcha\\' => array($vendorDir . '/edward1108/edward-captcha/src'), |
|||
'app\\' => array($baseDir . '/app'), |
|||
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), |
|||
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'), |
|||
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), |
|||
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), |
|||
'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'), |
|||
'Symfony\\Polyfill\\Iconv\\' => array($vendorDir . '/symfony/polyfill-iconv'), |
|||
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), |
|||
'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), |
|||
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'), |
|||
'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), |
|||
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), |
|||
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), |
|||
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), |
|||
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), |
|||
'Phinx\\' => array($vendorDir . '/topthink/think-migration/phinx/src/Phinx'), |
|||
'OpenApi\\' => array($vendorDir . '/zircote/swagger-php/src'), |
|||
'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'), |
|||
'League\\Flysystem\\Cached\\' => array($vendorDir . '/league/flysystem-cached-adapter/src'), |
|||
'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'), |
|||
'Lcobucci\\JWT\\' => array($vendorDir . '/lcobucci/jwt/src'), |
|||
'Lcobucci\\Clock\\' => array($vendorDir . '/lcobucci/clock/src'), |
|||
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'), |
|||
'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'), |
|||
'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'), |
|||
); |
|||
@ -0,0 +1,75 @@ |
|||
<?php |
|||
|
|||
// autoload_real.php @generated by Composer |
|||
|
|||
class ComposerAutoloaderInit9caf0e5e183c101daf530f1044c09467 |
|||
{ |
|||
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('ComposerAutoloaderInit9caf0e5e183c101daf530f1044c09467', 'loadClassLoader'), true, true); |
|||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); |
|||
spl_autoload_unregister(array('ComposerAutoloaderInit9caf0e5e183c101daf530f1044c09467', 'loadClassLoader')); |
|||
|
|||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); |
|||
if ($useStaticLoader) { |
|||
require __DIR__ . '/autoload_static.php'; |
|||
|
|||
call_user_func(\Composer\Autoload\ComposerStaticInit9caf0e5e183c101daf530f1044c09467::getInitializer($loader)); |
|||
} else { |
|||
$map = require __DIR__ . '/autoload_namespaces.php'; |
|||
foreach ($map as $namespace => $path) { |
|||
$loader->set($namespace, $path); |
|||
} |
|||
|
|||
$map = require __DIR__ . '/autoload_psr4.php'; |
|||
foreach ($map as $namespace => $path) { |
|||
$loader->setPsr4($namespace, $path); |
|||
} |
|||
|
|||
$classMap = require __DIR__ . '/autoload_classmap.php'; |
|||
if ($classMap) { |
|||
$loader->addClassMap($classMap); |
|||
} |
|||
} |
|||
|
|||
$loader->register(true); |
|||
|
|||
if ($useStaticLoader) { |
|||
$includeFiles = Composer\Autoload\ComposerStaticInit9caf0e5e183c101daf530f1044c09467::$files; |
|||
} else { |
|||
$includeFiles = require __DIR__ . '/autoload_files.php'; |
|||
} |
|||
foreach ($includeFiles as $fileIdentifier => $file) { |
|||
composerRequire9caf0e5e183c101daf530f1044c09467($fileIdentifier, $file); |
|||
} |
|||
|
|||
return $loader; |
|||
} |
|||
} |
|||
|
|||
function composerRequire9caf0e5e183c101daf530f1044c09467($fileIdentifier, $file) |
|||
{ |
|||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { |
|||
require $file; |
|||
|
|||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; |
|||
} |
|||
} |
|||
@ -0,0 +1,234 @@ |
|||
<?php |
|||
|
|||
// autoload_static.php @generated by Composer |
|||
|
|||
namespace Composer\Autoload; |
|||
|
|||
class ComposerStaticInit9caf0e5e183c101daf530f1044c09467 |
|||
{ |
|||
public static $files = array ( |
|||
'9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php', |
|||
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', |
|||
'35fab96057f1bf5e7aba31a8a6d5fdde' => __DIR__ . '/..' . '/topthink/think-orm/stubs/load_stubs.php', |
|||
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', |
|||
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', |
|||
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', |
|||
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', |
|||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', |
|||
'def43f6c87e4f8dfd0c9e1b1bab14fe8' => __DIR__ . '/..' . '/symfony/polyfill-iconv/bootstrap.php', |
|||
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', |
|||
'b2759b3fbd06631840a9638abcd22485' => __DIR__ . '/..' . '/edward1108/edward-captcha/src/helper.php', |
|||
'2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php', |
|||
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', |
|||
'0ccdf99b8f62f02c52cba55802e0c2e7' => __DIR__ . '/..' . '/zircote/swagger-php/src/functions.php', |
|||
); |
|||
|
|||
public static $prefixLengthsPsr4 = array ( |
|||
't' => |
|||
array ( |
|||
'think\\trace\\' => 12, |
|||
'think\\migration\\' => 16, |
|||
'think\\app\\' => 10, |
|||
'think\\' => 6, |
|||
), |
|||
'e' => |
|||
array ( |
|||
'edward\\captcha\\' => 15, |
|||
), |
|||
'a' => |
|||
array ( |
|||
'app\\' => 4, |
|||
), |
|||
'S' => |
|||
array ( |
|||
'Symfony\\Polyfill\\Php80\\' => 23, |
|||
'Symfony\\Polyfill\\Php72\\' => 23, |
|||
'Symfony\\Polyfill\\Mbstring\\' => 26, |
|||
'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, |
|||
'Symfony\\Polyfill\\Intl\\Idn\\' => 26, |
|||
'Symfony\\Polyfill\\Iconv\\' => 23, |
|||
'Symfony\\Polyfill\\Ctype\\' => 23, |
|||
'Symfony\\Component\\Yaml\\' => 23, |
|||
'Symfony\\Component\\VarDumper\\' => 28, |
|||
'Symfony\\Component\\Finder\\' => 25, |
|||
), |
|||
'P' => |
|||
array ( |
|||
'Psr\\SimpleCache\\' => 16, |
|||
'Psr\\Log\\' => 8, |
|||
'Psr\\Container\\' => 14, |
|||
'Psr\\Cache\\' => 10, |
|||
'Phinx\\' => 6, |
|||
), |
|||
'O' => |
|||
array ( |
|||
'OpenApi\\' => 8, |
|||
), |
|||
'L' => |
|||
array ( |
|||
'League\\MimeTypeDetection\\' => 25, |
|||
'League\\Flysystem\\Cached\\' => 24, |
|||
'League\\Flysystem\\' => 17, |
|||
'Lcobucci\\JWT\\' => 13, |
|||
'Lcobucci\\Clock\\' => 15, |
|||
), |
|||
'E' => |
|||
array ( |
|||
'Egulias\\EmailValidator\\' => 23, |
|||
), |
|||
'D' => |
|||
array ( |
|||
'Doctrine\\Common\\Lexer\\' => 22, |
|||
'Doctrine\\Common\\Annotations\\' => 28, |
|||
), |
|||
); |
|||
|
|||
public static $prefixDirsPsr4 = array ( |
|||
'think\\trace\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/topthink/think-trace/src', |
|||
), |
|||
'think\\migration\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/topthink/think-migration/src', |
|||
), |
|||
'think\\app\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/topthink/think-multi-app/src', |
|||
), |
|||
'think\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/topthink/framework/src/think', |
|||
1 => __DIR__ . '/..' . '/topthink/think-helper/src', |
|||
2 => __DIR__ . '/..' . '/topthink/think-orm/src', |
|||
), |
|||
'edward\\captcha\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/edward1108/edward-captcha/src', |
|||
), |
|||
'app\\' => |
|||
array ( |
|||
0 => __DIR__ . '/../..' . '/app', |
|||
), |
|||
'Symfony\\Polyfill\\Php80\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/symfony/polyfill-php80', |
|||
), |
|||
'Symfony\\Polyfill\\Php72\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/symfony/polyfill-php72', |
|||
), |
|||
'Symfony\\Polyfill\\Mbstring\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', |
|||
), |
|||
'Symfony\\Polyfill\\Intl\\Normalizer\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer', |
|||
), |
|||
'Symfony\\Polyfill\\Intl\\Idn\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn', |
|||
), |
|||
'Symfony\\Polyfill\\Iconv\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/symfony/polyfill-iconv', |
|||
), |
|||
'Symfony\\Polyfill\\Ctype\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', |
|||
), |
|||
'Symfony\\Component\\Yaml\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/symfony/yaml', |
|||
), |
|||
'Symfony\\Component\\VarDumper\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/symfony/var-dumper', |
|||
), |
|||
'Symfony\\Component\\Finder\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/symfony/finder', |
|||
), |
|||
'Psr\\SimpleCache\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/psr/simple-cache/src', |
|||
), |
|||
'Psr\\Log\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/psr/log/Psr/Log', |
|||
), |
|||
'Psr\\Container\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/psr/container/src', |
|||
), |
|||
'Psr\\Cache\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/psr/cache/src', |
|||
), |
|||
'Phinx\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/topthink/think-migration/phinx/src/Phinx', |
|||
), |
|||
'OpenApi\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/zircote/swagger-php/src', |
|||
), |
|||
'League\\MimeTypeDetection\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/league/mime-type-detection/src', |
|||
), |
|||
'League\\Flysystem\\Cached\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src', |
|||
), |
|||
'League\\Flysystem\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/league/flysystem/src', |
|||
), |
|||
'Lcobucci\\JWT\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/lcobucci/jwt/src', |
|||
), |
|||
'Lcobucci\\Clock\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/lcobucci/clock/src', |
|||
), |
|||
'Egulias\\EmailValidator\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/egulias/email-validator/src', |
|||
), |
|||
'Doctrine\\Common\\Lexer\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer', |
|||
), |
|||
'Doctrine\\Common\\Annotations\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations', |
|||
), |
|||
); |
|||
|
|||
public static $fallbackDirsPsr0 = array ( |
|||
0 => __DIR__ . '/../..' . '/extend', |
|||
); |
|||
|
|||
public static $classMap = array ( |
|||
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', |
|||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', |
|||
'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', |
|||
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', |
|||
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', |
|||
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', |
|||
); |
|||
|
|||
public static function getInitializer(ClassLoader $loader) |
|||
{ |
|||
return \Closure::bind(function () use ($loader) { |
|||
$loader->prefixLengthsPsr4 = ComposerStaticInit9caf0e5e183c101daf530f1044c09467::$prefixLengthsPsr4; |
|||
$loader->prefixDirsPsr4 = ComposerStaticInit9caf0e5e183c101daf530f1044c09467::$prefixDirsPsr4; |
|||
$loader->fallbackDirsPsr0 = ComposerStaticInit9caf0e5e183c101daf530f1044c09467::$fallbackDirsPsr0; |
|||
$loader->classMap = ComposerStaticInit9caf0e5e183c101daf530f1044c09467::$classMap; |
|||
|
|||
}, null, ClassLoader::class); |
|||
} |
|||
} |
|||
@ -0,0 +1,312 @@ |
|||
<?php return array ( |
|||
'root' => |
|||
array ( |
|||
'pretty_version' => 'dev-master', |
|||
'version' => 'dev-master', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'e60cfe2b6616b1bcb1c91f148a81369e11161013', |
|||
'name' => 'topthink/think', |
|||
), |
|||
'versions' => |
|||
array ( |
|||
'doctrine/annotations' => |
|||
array ( |
|||
'pretty_version' => '1.12.1', |
|||
'version' => '1.12.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'b17c5014ef81d212ac539f07a1001832df1b6d3b', |
|||
), |
|||
'doctrine/lexer' => |
|||
array ( |
|||
'pretty_version' => '1.2.1', |
|||
'version' => '1.2.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'e864bbf5904cb8f5bb334f99209b48018522f042', |
|||
), |
|||
'edward1108/edward-captcha' => |
|||
array ( |
|||
'pretty_version' => '1.1', |
|||
'version' => '1.1.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '426ceee34507c30d4b21e0dd349b571371aef700', |
|||
), |
|||
'egulias/email-validator' => |
|||
array ( |
|||
'pretty_version' => '2.1.25', |
|||
'version' => '2.1.25.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '0dbf5d78455d4d6a41d186da50adc1122ec066f4', |
|||
), |
|||
'lcobucci/clock' => |
|||
array ( |
|||
'pretty_version' => '2.0.0', |
|||
'version' => '2.0.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '353d83fe2e6ae95745b16b3d911813df6a05bfb3', |
|||
), |
|||
'lcobucci/jwt' => |
|||
array ( |
|||
'pretty_version' => '4.1.2', |
|||
'version' => '4.1.2.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'c544710aa18e079baf0027ca4c8236913f46945b', |
|||
), |
|||
'league/flysystem' => |
|||
array ( |
|||
'pretty_version' => '1.1.3', |
|||
'version' => '1.1.3.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '9be3b16c877d477357c015cec057548cf9b2a14a', |
|||
), |
|||
'league/flysystem-cached-adapter' => |
|||
array ( |
|||
'pretty_version' => '1.1.0', |
|||
'version' => '1.1.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'd1925efb2207ac4be3ad0c40b8277175f99ffaff', |
|||
), |
|||
'league/mime-type-detection' => |
|||
array ( |
|||
'pretty_version' => '1.7.0', |
|||
'version' => '1.7.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3', |
|||
), |
|||
'psr/cache' => |
|||
array ( |
|||
'pretty_version' => '1.0.1', |
|||
'version' => '1.0.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8', |
|||
), |
|||
'psr/container' => |
|||
array ( |
|||
'pretty_version' => '1.0.0', |
|||
'version' => '1.0.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'b7ce3b176482dbbc1245ebf52b181af44c2cf55f', |
|||
), |
|||
'psr/log' => |
|||
array ( |
|||
'pretty_version' => '1.1.3', |
|||
'version' => '1.1.3.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '0f73288fd15629204f9d42b7055f72dacbe811fc', |
|||
), |
|||
'psr/simple-cache' => |
|||
array ( |
|||
'pretty_version' => '1.0.1', |
|||
'version' => '1.0.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b', |
|||
), |
|||
'swiftmailer/swiftmailer' => |
|||
array ( |
|||
'pretty_version' => 'v6.2.5', |
|||
'version' => '6.2.5.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '698a6a9f54d7eb321274de3ad19863802c879fb7', |
|||
), |
|||
'symfony/deprecation-contracts' => |
|||
array ( |
|||
'pretty_version' => 'v2.2.0', |
|||
'version' => '2.2.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '5fa56b4074d1ae755beb55617ddafe6f5d78f665', |
|||
), |
|||
'symfony/finder' => |
|||
array ( |
|||
'pretty_version' => 'v5.2.4', |
|||
'version' => '5.2.4.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '0d639a0943822626290d169965804f79400e6a04', |
|||
), |
|||
'symfony/polyfill-ctype' => |
|||
array ( |
|||
'pretty_version' => 'v1.22.1', |
|||
'version' => '1.22.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'c6c942b1ac76c82448322025e084cadc56048b4e', |
|||
), |
|||
'symfony/polyfill-iconv' => |
|||
array ( |
|||
'pretty_version' => 'v1.22.1', |
|||
'version' => '1.22.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '06fb361659649bcfd6a208a0f1fcaf4e827ad342', |
|||
), |
|||
'symfony/polyfill-intl-idn' => |
|||
array ( |
|||
'pretty_version' => 'v1.22.1', |
|||
'version' => '1.22.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '2d63434d922daf7da8dd863e7907e67ee3031483', |
|||
), |
|||
'symfony/polyfill-intl-normalizer' => |
|||
array ( |
|||
'pretty_version' => 'v1.22.1', |
|||
'version' => '1.22.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '43a0283138253ed1d48d352ab6d0bdb3f809f248', |
|||
), |
|||
'symfony/polyfill-mbstring' => |
|||
array ( |
|||
'pretty_version' => 'v1.22.1', |
|||
'version' => '1.22.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '5232de97ee3b75b0360528dae24e73db49566ab1', |
|||
), |
|||
'symfony/polyfill-php72' => |
|||
array ( |
|||
'pretty_version' => 'v1.22.1', |
|||
'version' => '1.22.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9', |
|||
), |
|||
'symfony/polyfill-php80' => |
|||
array ( |
|||
'pretty_version' => 'v1.22.1', |
|||
'version' => '1.22.1.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'dc3063ba22c2a1fd2f45ed856374d79114998f91', |
|||
), |
|||
'symfony/var-dumper' => |
|||
array ( |
|||
'pretty_version' => 'v4.4.20', |
|||
'version' => '4.4.20.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'a1eab2f69906dc83c5ddba4632180260d0ab4f7f', |
|||
), |
|||
'symfony/yaml' => |
|||
array ( |
|||
'pretty_version' => 'v5.2.4', |
|||
'version' => '5.2.4.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '7d6ae0cce3c33965af681a4355f1c4de326ed277', |
|||
), |
|||
'topthink/framework' => |
|||
array ( |
|||
'pretty_version' => 'v6.0.7', |
|||
'version' => '6.0.7.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'db8fe22520a9660dd5e4c87e304034ac49e39270', |
|||
), |
|||
'topthink/think' => |
|||
array ( |
|||
'pretty_version' => 'dev-master', |
|||
'version' => 'dev-master', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'e60cfe2b6616b1bcb1c91f148a81369e11161013', |
|||
), |
|||
'topthink/think-helper' => |
|||
array ( |
|||
'pretty_version' => 'v3.1.4', |
|||
'version' => '3.1.4.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'c28d37743bda4a0455286ca85b17b5791d626e10', |
|||
), |
|||
'topthink/think-migration' => |
|||
array ( |
|||
'pretty_version' => 'v3.0.3', |
|||
'version' => '3.0.3.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '5717d9e5f3ea745f6dbfd1e30b4402aaadff9a79', |
|||
), |
|||
'topthink/think-multi-app' => |
|||
array ( |
|||
'pretty_version' => 'v1.0.14', |
|||
'version' => '1.0.14.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => 'ccaad7c2d33f42cb1cc2a78d6610aaec02cea4c3', |
|||
), |
|||
'topthink/think-orm' => |
|||
array ( |
|||
'pretty_version' => 'v2.0.39', |
|||
'version' => '2.0.39.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '39a9d0a0e52d9b8bad9d98484d8484cdf5b683a7', |
|||
), |
|||
'topthink/think-trace' => |
|||
array ( |
|||
'pretty_version' => 'v1.4', |
|||
'version' => '1.4.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '9a9fa8f767b6c66c5a133ad21ca1bc96ad329444', |
|||
), |
|||
'zircote/swagger-php' => |
|||
array ( |
|||
'pretty_version' => '3.1.0', |
|||
'version' => '3.1.0.0', |
|||
'aliases' => |
|||
array ( |
|||
), |
|||
'reference' => '9d172471e56433b5c7061006b9a766f262a3edfd', |
|||
), |
|||
), |
|||
); |
|||
@ -0,0 +1,26 @@ |
|||
<?php |
|||
|
|||
// platform_check.php @generated by Composer |
|||
|
|||
$issues = array(); |
|||
|
|||
if (!(PHP_VERSION_ID >= 70400)) { |
|||
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". 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 |
|||
); |
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
## Changelog |
|||
|
|||
### 1.6.1 |
|||
|
|||
This release fixes an issue in which annotations such as `@foo-bar` |
|||
and `@foo-` were incorrectly recognised as valid, and both erroneously |
|||
parsed as `@foo`. |
|||
|
|||
Any annotation with `@name-*` format will now silently be ignored, |
|||
allowing vendor-specific annotations to be prefixed with the tool |
|||
name. |
|||
|
|||
Total issues resolved: **3** |
|||
|
|||
- [165: Update the composer branch alias](https://github.com/doctrine/annotations/pull/165) thanks to @mikeSimonson |
|||
- [209: Change Annotation::value typehint to mixed](https://github.com/doctrine/annotations/pull/209) thanks to @malarzm |
|||
- [257: Skip parsing annotations containing dashes, such as `@Foo-bar`, or `@Foo-`](https://github.com/doctrine/annotations/pull/257) thanks to @Ocramius |
|||
|
|||
### 1.6.0 |
|||
|
|||
This release brings a new endpoint that make sure that you can't shoot yourself in the foot by calling ```registerLoader``` multiple times and a few tests improvements. |
|||
|
|||
Total issues resolved: **7** |
|||
|
|||
- [145: Memory leak in AnnotationRegistry::registerLoader() when called multiple times](https://github.com/doctrine/annotations/issues/145) thanks to @TriAnMan |
|||
- [146: Import error on @experimental Annotation](https://github.com/doctrine/annotations/issues/146) thanks to @aturki |
|||
- [147: Ignoring @experimental annotation used by Symfony 3.3 CacheAdapter](https://github.com/doctrine/annotations/pull/147) thanks to @aturki |
|||
- [151: Remove duplicate code in `DCOM58Test`](https://github.com/doctrine/annotations/pull/151) thanks to @tuanphpvn |
|||
- [161: Prevent loading class_exists multiple times](https://github.com/doctrine/annotations/pull/161) thanks to @jrjohnson |
|||
- [162: Add registerUniqueLoader to AnnotationRegistry](https://github.com/doctrine/annotations/pull/162) thanks to @jrjohnson |
|||
- [163: Use assertDirectoryExists and assertDirectoryNotExists](https://github.com/doctrine/annotations/pull/163) thanks to @carusogabriel |
|||
|
|||
Thanks to everyone involved in this release. |
|||
|
|||
### 1.5.0 |
|||
|
|||
This release increments the minimum supported PHP version to 7.1.0. |
|||
|
|||
Also, HHVM official support has been dropped. |
|||
|
|||
Some noticeable performance improvements to annotation autoloading |
|||
have been applied, making failed annotation autoloading less heavy |
|||
on the filesystem access. |
|||
|
|||
- [133: Add @throws annotation in AnnotationReader#__construct()](https://github.com/doctrine/annotations/issues/133) thanks to @SenseException |
|||
- [134: Require PHP 7.1, drop HHVM support](https://github.com/doctrine/annotations/issues/134) thanks to @lcobucci |
|||
- [135: Prevent the same loader from being registered twice](https://github.com/doctrine/annotations/issues/135) thanks to @jrjohnson |
|||
- [137: #135 optimise multiple class load attempts in AnnotationRegistry](https://github.com/doctrine/annotations/issues/137) thanks to @Ocramius |
|||
|
|||
|
|||
### 1.4.0 |
|||
|
|||
This release fix an issue were some annotations could be not loaded if the namespace in the use statement started with a backslash. |
|||
It also update the tests and drop the support for php 5.X |
|||
|
|||
- [115: Missing annotations with the latest composer version](https://github.com/doctrine/annotations/issues/115) thanks to @pascalporedda |
|||
- [120: Missing annotations with the latest composer version](https://github.com/doctrine/annotations/pull/120) thanks to @gnat42 |
|||
- [121: Adding a more detailed explanation of the test](https://github.com/doctrine/annotations/pull/121) thanks to @mikeSimonson |
|||
- [101: Test annotation parameters containing space](https://github.com/doctrine/annotations/pull/101) thanks to @mikeSimonson |
|||
- [111: Cleanup: move to correct phpunit assertions](https://github.com/doctrine/annotations/pull/111) thanks to @Ocramius |
|||
- [112: Removes support for PHP 5.x](https://github.com/doctrine/annotations/pull/112) thanks to @railto |
|||
- [113: bumped phpunit version to 5.7](https://github.com/doctrine/annotations/pull/113) thanks to @gabbydgab |
|||
- [114: Enhancement: Use SVG Travis build badge](https://github.com/doctrine/annotations/pull/114) thanks to @localheinz |
|||
- [118: Integrating PHPStan](https://github.com/doctrine/annotations/pull/118) thanks to @ondrejmirtes |
|||
|
|||
### 1.3.1 - 2016-12-30 |
|||
|
|||
This release fixes an issue with ignored annotations that were already |
|||
autoloaded, causing the `SimpleAnnotationReader` to pick them up |
|||
anyway. [#110](https://github.com/doctrine/annotations/pull/110) |
|||
|
|||
Additionally, an issue was fixed in the `CachedReader`, which was |
|||
not correctly checking the freshness of cached annotations when |
|||
traits were defined on a class. [#105](https://github.com/doctrine/annotations/pull/105) |
|||
|
|||
Total issues resolved: **2** |
|||
|
|||
- [105: Return single max timestamp](https://github.com/doctrine/annotations/pull/105) |
|||
- [110: setIgnoreNotImportedAnnotations(true) didn’t work for existing classes](https://github.com/doctrine/annotations/pull/110) |
|||
|
|||
### 1.3.0 |
|||
|
|||
This release introduces a PHP version bump. `doctrine/annotations` now requires PHP |
|||
5.6 or later to be installed. |
|||
|
|||
A series of additional improvements have been introduced: |
|||
|
|||
* support for PHP 7 "grouped use statements" |
|||
* support for ignoring entire namespace names |
|||
via `Doctrine\Common\Annotations\AnnotationReader::addGlobalIgnoredNamespace()` and |
|||
`Doctrine\Common\Annotations\DocParser::setIgnoredAnnotationNamespaces()`. This will |
|||
allow you to ignore annotations from namespaces that you cannot autoload |
|||
* testing all parent classes and interfaces when checking if the annotation cache |
|||
in the `CachedReader` is fresh |
|||
* simplifying the cache keys used by the `CachedReader`: keys are no longer artificially |
|||
namespaced, since `Doctrine\Common\Cache` already supports that |
|||
* corrected parsing of multibyte strings when `mbstring.func_overload` is enabled |
|||
* corrected parsing of annotations when `"\t"` is put before the first annotation |
|||
in a docblock |
|||
* allow skipping non-imported annotations when a custom `DocParser` is passed to |
|||
the `AnnotationReader` constructor |
|||
|
|||
Total issues resolved: **15** |
|||
|
|||
- [45: DocParser can now ignore whole namespaces](https://github.com/doctrine/annotations/pull/45) |
|||
- [57: Switch to the docker-based infrastructure on Travis](https://github.com/doctrine/annotations/pull/57) |
|||
- [59: opcache.load_comments has been removed from PHP 7](https://github.com/doctrine/annotations/pull/59) |
|||
- [62: [CachedReader\ Test traits and parent class to see if cache is fresh](https://github.com/doctrine/annotations/pull/62) |
|||
- [65: Remove cache salt making key unnecessarily long](https://github.com/doctrine/annotations/pull/65) |
|||
- [66: Fix of incorrect parsing multibyte strings](https://github.com/doctrine/annotations/pull/66) |
|||
- [68: Annotations that are indented by tab are not processed.](https://github.com/doctrine/annotations/issues/68) |
|||
- [69: Support for Group Use Statements](https://github.com/doctrine/annotations/pull/69) |
|||
- [70: Allow tab character before first annotation in DocBlock](https://github.com/doctrine/annotations/pull/70) |
|||
- [74: Ignore not registered annotations fix](https://github.com/doctrine/annotations/pull/74) |
|||
- [92: Added tests for AnnotationRegistry class.](https://github.com/doctrine/annotations/pull/92) |
|||
- [96: Fix/#62 check trait and parent class ttl in annotations](https://github.com/doctrine/annotations/pull/96) |
|||
- [97: Feature - #45 - allow ignoring entire namespaces](https://github.com/doctrine/annotations/pull/97) |
|||
- [98: Enhancement/#65 remove cache salt from cached reader](https://github.com/doctrine/annotations/pull/98) |
|||
- [99: Fix - #70 - allow tab character before first annotation in docblock](https://github.com/doctrine/annotations/pull/99) |
|||
|
|||
### 1.2.4 |
|||
|
|||
Total issues resolved: **1** |
|||
|
|||
- [51: FileCacheReader::saveCacheFile::unlink fix](https://github.com/doctrine/annotations/pull/51) |
|||
|
|||
### 1.2.3 |
|||
|
|||
Total issues resolved: [**2**](https://github.com/doctrine/annotations/milestones/v1.2.3) |
|||
|
|||
- [49: #46 - applying correct `chmod()` to generated cache file](https://github.com/doctrine/annotations/pull/49) |
|||
- [50: Hotfix: match escaped quotes (revert #44)](https://github.com/doctrine/annotations/pull/50) |
|||
|
|||
### 1.2.2 |
|||
|
|||
Total issues resolved: **4** |
|||
|
|||
- [43: Exclude files from distribution with .gitattributes](https://github.com/doctrine/annotations/pull/43) |
|||
- [44: Update DocLexer.php](https://github.com/doctrine/annotations/pull/44) |
|||
- [46: A plain "file_put_contents" can cause havoc](https://github.com/doctrine/annotations/pull/46) |
|||
- [48: Deprecating the `FileCacheReader` in 1.2.2: will be removed in 2.0.0](https://github.com/doctrine/annotations/pull/48) |
|||
|
|||
### 1.2.1 |
|||
|
|||
Total issues resolved: **4** |
|||
|
|||
- [38: fixes doctrine/common#326](https://github.com/doctrine/annotations/pull/38) |
|||
- [39: Remove superfluous NS](https://github.com/doctrine/annotations/pull/39) |
|||
- [41: Warn if load_comments is not enabled.](https://github.com/doctrine/annotations/pull/41) |
|||
- [42: Clean up unused uses](https://github.com/doctrine/annotations/pull/42) |
|||
|
|||
### 1.2.0 |
|||
|
|||
* HHVM support |
|||
* Allowing dangling comma in annotations |
|||
* Excluded annotations are no longer autoloaded |
|||
* Importing namespaces also in traits |
|||
* Added support for `::class` 5.5-style constant, works also in 5.3 and 5.4 |
|||
|
|||
### 1.1.0 |
|||
|
|||
* Add Exception when ZendOptimizer+ or Opcache is configured to drop comments |
|||
@ -0,0 +1,19 @@ |
|||
Copyright (c) 2006-2013 Doctrine Project |
|||
|
|||
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. |
|||
@ -0,0 +1,22 @@ |
|||
# Doctrine Annotations |
|||
|
|||
[](https://github.com/doctrine/persistence/actions) |
|||
[](https://www.versioneye.com/package/php--doctrine--annotations) |
|||
[](https://www.versioneye.com/php/doctrine:annotations/references) |
|||
[](https://packagist.org/packages/doctrine/annotations) |
|||
[](https://packagist.org/packages/doctrine/annotations) |
|||
|
|||
Docblock Annotations Parser library (extracted from [Doctrine Common](https://github.com/doctrine/common)). |
|||
|
|||
## Documentation |
|||
|
|||
See the [doctrine-project website](https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html). |
|||
|
|||
## Contributing |
|||
|
|||
When making a pull request, make sure your changes follow the |
|||
[Coding Standard Guidelines](https://www.doctrine-project.org/projects/doctrine-coding-standard/en/current/reference/index.html#introduction). |
|||
|
|||
## Changelog |
|||
|
|||
See [CHANGELOG.md](CHANGELOG.md). |
|||
@ -0,0 +1,42 @@ |
|||
{ |
|||
"name": "doctrine/annotations", |
|||
"type": "library", |
|||
"description": "Docblock Annotations Parser", |
|||
"keywords": ["annotations", "docblock", "parser"], |
|||
"homepage": "https://www.doctrine-project.org/projects/annotations.html", |
|||
"license": "MIT", |
|||
"authors": [ |
|||
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, |
|||
{"name": "Roman Borschel", "email": "roman@code-factory.org"}, |
|||
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, |
|||
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, |
|||
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} |
|||
], |
|||
"require": { |
|||
"php": "^7.1 || ^8.0", |
|||
"ext-tokenizer": "*", |
|||
"doctrine/lexer": "1.*" |
|||
}, |
|||
"require-dev": { |
|||
"doctrine/cache": "1.*", |
|||
"doctrine/coding-standard": "^6.0 || ^8.1", |
|||
"phpstan/phpstan": "^0.12.20", |
|||
"phpunit/phpunit": "^7.5 || ^9.1.5" |
|||
}, |
|||
"config": { |
|||
"sort-packages": true |
|||
}, |
|||
"autoload": { |
|||
"psr-4": { "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" } |
|||
}, |
|||
"autoload-dev": { |
|||
"psr-4": { |
|||
"Doctrine\\Performance\\Common\\Annotations\\": "tests/Doctrine/Performance/Common/Annotations", |
|||
"Doctrine\\Tests\\Common\\Annotations\\": "tests/Doctrine/Tests/Common/Annotations" |
|||
}, |
|||
"files": [ |
|||
"tests/Doctrine/Tests/Common/Annotations/Fixtures/functions.php", |
|||
"tests/Doctrine/Tests/Common/Annotations/Fixtures/SingleClassLOC1000.php" |
|||
] |
|||
} |
|||
} |
|||
@ -0,0 +1,271 @@ |
|||
Handling Annotations |
|||
==================== |
|||
|
|||
There are several different approaches to handling annotations in PHP. |
|||
Doctrine Annotations maps docblock annotations to PHP classes. Because |
|||
not all docblock annotations are used for metadata purposes a filter is |
|||
applied to ignore or skip classes that are not Doctrine annotations. |
|||
|
|||
Take a look at the following code snippet: |
|||
|
|||
.. code-block:: php |
|||
|
|||
namespace MyProject\Entities; |
|||
|
|||
use Doctrine\ORM\Mapping AS ORM; |
|||
use Symfony\Component\Validator\Constraints AS Assert; |
|||
|
|||
/** |
|||
* @author Benjamin Eberlei |
|||
* @ORM\Entity |
|||
* @MyProject\Annotations\Foobarable |
|||
*/ |
|||
class User |
|||
{ |
|||
/** |
|||
* @ORM\Id @ORM\Column @ORM\GeneratedValue |
|||
* @dummy |
|||
* @var int |
|||
*/ |
|||
private $id; |
|||
|
|||
/** |
|||
* @ORM\Column(type="string") |
|||
* @Assert\NotEmpty |
|||
* @Assert\Email |
|||
* @var string |
|||
*/ |
|||
private $email; |
|||
} |
|||
|
|||
In this snippet you can see a variety of different docblock annotations: |
|||
|
|||
- Documentation annotations such as ``@var`` and ``@author``. These |
|||
annotations are ignored and never considered for throwing an |
|||
exception due to wrongly used annotations. |
|||
- Annotations imported through use statements. The statement ``use |
|||
Doctrine\ORM\Mapping AS ORM`` makes all classes under that namespace |
|||
available as ``@ORM\ClassName``. Same goes for the import of |
|||
``@Assert``. |
|||
- The ``@dummy`` annotation. It is not a documentation annotation and |
|||
not ignored. For Doctrine Annotations it is not entirely clear how |
|||
to handle this annotation. Depending on the configuration an exception |
|||
(unknown annotation) will be thrown when parsing this annotation. |
|||
- The fully qualified annotation ``@MyProject\Annotations\Foobarable``. |
|||
This is transformed directly into the given class name. |
|||
|
|||
How are these annotations loaded? From looking at the code you could |
|||
guess that the ORM Mapping, Assert Validation and the fully qualified |
|||
annotation can just be loaded using |
|||
the defined PHP autoloaders. This is not the case however: For error |
|||
handling reasons every check for class existence inside the |
|||
``AnnotationReader`` sets the second parameter $autoload |
|||
of ``class_exists($name, $autoload)`` to false. To work flawlessly the |
|||
``AnnotationReader`` requires silent autoloaders which many autoloaders are |
|||
not. Silent autoloading is NOT part of the `PSR-0 specification |
|||
<https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md>`_ |
|||
for autoloading. |
|||
|
|||
This is why Doctrine Annotations uses its own autoloading mechanism |
|||
through a global registry. If you are wondering about the annotation |
|||
registry being global, there is no other way to solve the architectural |
|||
problems of autoloading annotation classes in a straightforward fashion. |
|||
Additionally if you think about PHP autoloading then you recognize it is |
|||
a global as well. |
|||
|
|||
To anticipate the configuration section, making the above PHP class work |
|||
with Doctrine Annotations requires this setup: |
|||
|
|||
.. code-block:: php |
|||
|
|||
use Doctrine\Common\Annotations\AnnotationReader; |
|||
use Doctrine\Common\Annotations\AnnotationRegistry; |
|||
|
|||
AnnotationRegistry::registerFile("/path/to/doctrine/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php"); |
|||
AnnotationRegistry::registerAutoloadNamespace("Symfony\Component\Validator\Constraint", "/path/to/symfony/src"); |
|||
AnnotationRegistry::registerAutoloadNamespace("MyProject\Annotations", "/path/to/myproject/src"); |
|||
|
|||
$reader = new AnnotationReader(); |
|||
AnnotationReader::addGlobalIgnoredName('dummy'); |
|||
|
|||
The second block with the annotation registry calls registers all the |
|||
three different annotation namespaces that are used. |
|||
Doctrine Annotations saves all its annotations in a single file, that is |
|||
why ``AnnotationRegistry#registerFile`` is used in contrast to |
|||
``AnnotationRegistry#registerAutoloadNamespace`` which creates a PSR-0 |
|||
compatible loading mechanism for class to file names. |
|||
|
|||
In the third block, we create the actual ``AnnotationReader`` instance. |
|||
Note that we also add ``dummy`` to the global list of ignored |
|||
annotations for which we do not throw exceptions. Setting this is |
|||
necessary in our example case, otherwise ``@dummy`` would trigger an |
|||
exception to be thrown during the parsing of the docblock of |
|||
``MyProject\Entities\User#id``. |
|||
|
|||
Setup and Configuration |
|||
----------------------- |
|||
|
|||
To use the annotations library is simple, you just need to create a new |
|||
``AnnotationReader`` instance: |
|||
|
|||
.. code-block:: php |
|||
|
|||
$reader = new \Doctrine\Common\Annotations\AnnotationReader(); |
|||
|
|||
This creates a simple annotation reader with no caching other than in |
|||
memory (in php arrays). Since parsing docblocks can be expensive you |
|||
should cache this process by using a caching reader. |
|||
|
|||
You can use a file caching reader, but please note it is deprecated to |
|||
do so: |
|||
|
|||
.. code-block:: php |
|||
|
|||
use Doctrine\Common\Annotations\FileCacheReader; |
|||
use Doctrine\Common\Annotations\AnnotationReader; |
|||
|
|||
$reader = new FileCacheReader( |
|||
new AnnotationReader(), |
|||
"/path/to/cache", |
|||
$debug = true |
|||
); |
|||
|
|||
If you set the ``debug`` flag to ``true`` the cache reader will check |
|||
for changes in the original files, which is very important during |
|||
development. If you don't set it to ``true`` you have to delete the |
|||
directory to clear the cache. This gives faster performance, however |
|||
should only be used in production, because of its inconvenience during |
|||
development. |
|||
|
|||
You can also use one of the ``Doctrine\Common\Cache\Cache`` cache |
|||
implementations to cache the annotations: |
|||
|
|||
.. code-block:: php |
|||
|
|||
use Doctrine\Common\Annotations\AnnotationReader; |
|||
use Doctrine\Common\Annotations\CachedReader; |
|||
use Doctrine\Common\Cache\ApcCache; |
|||
|
|||
$reader = new CachedReader( |
|||
new AnnotationReader(), |
|||
new ApcCache(), |
|||
$debug = true |
|||
); |
|||
|
|||
The ``debug`` flag is used here as well to invalidate the cache files |
|||
when the PHP class with annotations changed and should be used during |
|||
development. |
|||
|
|||
.. warning :: |
|||
|
|||
The ``AnnotationReader`` works and caches under the |
|||
assumption that all annotations of a doc-block are processed at |
|||
once. That means that annotation classes that do not exist and |
|||
aren't loaded and cannot be autoloaded (using the |
|||
AnnotationRegistry) would never be visible and not accessible if a |
|||
cache is used unless the cache is cleared and the annotations |
|||
requested again, this time with all annotations defined. |
|||
|
|||
By default the annotation reader returns a list of annotations with |
|||
numeric indexes. If you want your annotations to be indexed by their |
|||
class name you can wrap the reader in an ``IndexedReader``: |
|||
|
|||
.. code-block:: php |
|||
|
|||
use Doctrine\Common\Annotations\AnnotationReader; |
|||
use Doctrine\Common\Annotations\IndexedReader; |
|||
|
|||
$reader = new IndexedReader(new AnnotationReader()); |
|||
|
|||
.. warning:: |
|||
|
|||
You should never wrap the indexed reader inside a cached reader, |
|||
only the other way around. This way you can re-use the cache with |
|||
indexed or numeric keys, otherwise your code may experience failures |
|||
due to caching in a numerical or indexed format. |
|||
|
|||
Registering Annotations |
|||
~~~~~~~~~~~~~~~~~~~~~~~ |
|||
|
|||
As explained in the introduction, Doctrine Annotations uses its own |
|||
autoloading mechanism to determine if a given annotation has a |
|||
corresponding PHP class that can be autoloaded. For annotation |
|||
autoloading you have to configure the |
|||
``Doctrine\Common\Annotations\AnnotationRegistry``. There are three |
|||
different mechanisms to configure annotation autoloading: |
|||
|
|||
- Calling ``AnnotationRegistry#registerFile($file)`` to register a file |
|||
that contains one or more annotation classes. |
|||
- Calling ``AnnotationRegistry#registerNamespace($namespace, $dirs = |
|||
null)`` to register that the given namespace contains annotations and |
|||
that their base directory is located at the given $dirs or in the |
|||
include path if ``NULL`` is passed. The given directories should *NOT* |
|||
be the directory where classes of the namespace are in, but the base |
|||
directory of the root namespace. The AnnotationRegistry uses a |
|||
namespace to directory separator approach to resolve the correct path. |
|||
- Calling ``AnnotationRegistry#registerLoader($callable)`` to register |
|||
an autoloader callback. The callback accepts the class as first and |
|||
only parameter and has to return ``true`` if the corresponding file |
|||
was found and included. |
|||
|
|||
.. note:: |
|||
|
|||
Loaders have to fail silently, if a class is not found even if it |
|||
matches for example the namespace prefix of that loader. Never is a |
|||
loader to throw a warning or exception if the loading failed |
|||
otherwise parsing doc block annotations will become a huge pain. |
|||
|
|||
A sample loader callback could look like: |
|||
|
|||
.. code-block:: php |
|||
|
|||
use Doctrine\Common\Annotations\AnnotationRegistry; |
|||
use Symfony\Component\ClassLoader\UniversalClassLoader; |
|||
|
|||
AnnotationRegistry::registerLoader(function($class) { |
|||
$file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php"; |
|||
|
|||
if (file_exists("/my/base/path/" . $file)) { |
|||
// file_exists() makes sure that the loader fails silently |
|||
require "/my/base/path/" . $file; |
|||
} |
|||
}); |
|||
|
|||
$loader = new UniversalClassLoader(); |
|||
AnnotationRegistry::registerLoader(array($loader, "loadClass")); |
|||
|
|||
|
|||
Ignoring missing exceptions |
|||
~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|||
|
|||
By default an exception is thrown from the ``AnnotationReader`` if an |
|||
annotation was found that: |
|||
|
|||
- is not part of the list of ignored "documentation annotations"; |
|||
- was not imported through a use statement; |
|||
- is not a fully qualified class that exists. |
|||
|
|||
You can disable this behavior for specific names if your docblocks do |
|||
not follow strict requirements: |
|||
|
|||
.. code-block:: php |
|||
|
|||
$reader = new \Doctrine\Common\Annotations\AnnotationReader(); |
|||
AnnotationReader::addGlobalIgnoredName('foo'); |
|||
|
|||
PHP Imports |
|||
~~~~~~~~~~~ |
|||
|
|||
By default the annotation reader parses the use-statement of a php file |
|||
to gain access to the import rules and register them for the annotation |
|||
processing. Only if you are using PHP Imports can you validate the |
|||
correct usage of annotations and throw exceptions if you misspelled an |
|||
annotation. This mechanism is enabled by default. |
|||
|
|||
To ease the upgrade path, we still allow you to disable this mechanism. |
|||
Note however that we will remove this in future versions: |
|||
|
|||
.. code-block:: php |
|||
|
|||
$reader = new \Doctrine\Common\Annotations\AnnotationReader(); |
|||
$reader->setEnabledPhpImports(false); |
|||
@ -0,0 +1,443 @@ |
|||
Custom Annotation Classes |
|||
========================= |
|||
|
|||
If you want to define your own annotations, you just have to group them |
|||
in a namespace and register this namespace in the ``AnnotationRegistry``. |
|||
Annotation classes have to contain a class-level docblock with the text |
|||
``@Annotation``: |
|||
|
|||
.. code-block:: php |
|||
|
|||
namespace MyCompany\Annotations; |
|||
|
|||
/** @Annotation */ |
|||
class Bar |
|||
{ |
|||
// some code |
|||
} |
|||
|
|||
Inject annotation values |
|||
------------------------ |
|||
|
|||
The annotation parser checks if the annotation constructor has arguments, |
|||
if so then it will pass the value array, otherwise it will try to inject |
|||
values into public properties directly: |
|||
|
|||
|
|||
.. code-block:: php |
|||
|
|||
namespace MyCompany\Annotations; |
|||
|
|||
/** |
|||
* @Annotation |
|||
* |
|||
* Some Annotation using a constructor |
|||
*/ |
|||
class Bar |
|||
{ |
|||
private $foo; |
|||
|
|||
public function __construct(array $values) |
|||
{ |
|||
$this->foo = $values['foo']; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @Annotation |
|||
* |
|||
* Some Annotation without a constructor |
|||
*/ |
|||
class Foo |
|||
{ |
|||
public $bar; |
|||
} |
|||
|
|||
Optional: Constructors with Named Parameters |
|||
-------------------------------------------- |
|||
|
|||
Starting with Annotations v1.11 a new annotation instantiation strategy |
|||
is available that aims at compatibility of Annotation classes with the PHP 8 |
|||
attribute feature. You need to declare a constructor with regular parameter |
|||
names that match the named arguments in the annotation syntax. |
|||
|
|||
To enable this feature, you can tag your annotation class with |
|||
``@NamedArgumentConstructor`` (available from v1.12) or implement the |
|||
``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` interface |
|||
(available from v1.11 and deprecated as of v1.12). |
|||
When using the ``@NamedArgumentConstructor`` tag, the first argument of the |
|||
constructor is considered as the default one. |
|||
|
|||
|
|||
Usage with the ``@NamedArgumentContrustor`` tag |
|||
|
|||
.. code-block:: php |
|||
|
|||
namespace MyCompany\Annotations; |
|||
|
|||
/** |
|||
* @Annotation |
|||
* @NamedArgumentConstructor |
|||
*/ |
|||
class Bar implements NamedArgumentConstructorAnnotation |
|||
{ |
|||
private $foo; |
|||
|
|||
public function __construct(string $foo) |
|||
{ |
|||
$this->foo = $foo; |
|||
} |
|||
} |
|||
|
|||
/** Usable with @Bar(foo="baz") */ |
|||
/** Usable with @Bar("baz") */ |
|||
|
|||
In combination with PHP 8's constructor property promotion feature |
|||
you can simplify this to: |
|||
|
|||
.. code-block:: php |
|||
|
|||
namespace MyCompany\Annotations; |
|||
|
|||
/** |
|||
* @Annotation |
|||
* @NamedArgumentConstructor |
|||
*/ |
|||
class Bar implements NamedArgumentConstructorAnnotation |
|||
{ |
|||
public function __construct(private string $foo) {} |
|||
} |
|||
|
|||
|
|||
Usage with the |
|||
``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` |
|||
interface (v1.11, deprecated as of v1.12): |
|||
.. code-block:: php |
|||
|
|||
namespace MyCompany\Annotations; |
|||
|
|||
use Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation; |
|||
|
|||
/** @Annotation */ |
|||
class Bar implements NamedArgumentConstructorAnnotation |
|||
{ |
|||
private $foo; |
|||
|
|||
public function __construct(private string $foo) {} |
|||
} |
|||
|
|||
/** Usable with @Bar(foo="baz") */ |
|||
|
|||
Annotation Target |
|||
----------------- |
|||
|
|||
``@Target`` indicates the kinds of class elements to which an annotation |
|||
type is applicable. Then you could define one or more targets: |
|||
|
|||
- ``CLASS`` Allowed in class docblocks |
|||
- ``PROPERTY`` Allowed in property docblocks |
|||
- ``METHOD`` Allowed in the method docblocks |
|||
- ``FUNCTION`` Allowed in function dockblocks |
|||
- ``ALL`` Allowed in class, property, method and function docblocks |
|||
- ``ANNOTATION`` Allowed inside other annotations |
|||
|
|||
If the annotations is not allowed in the current context, an |
|||
``AnnotationException`` is thrown. |
|||
|
|||
.. code-block:: php |
|||
|
|||
namespace MyCompany\Annotations; |
|||
|
|||
/** |
|||
* @Annotation |
|||
* @Target({"METHOD","PROPERTY"}) |
|||
*/ |
|||
class Bar |
|||
{ |
|||
// some code |
|||
} |
|||
|
|||
/** |
|||
* @Annotation |
|||
* @Target("CLASS") |
|||
*/ |
|||
class Foo |
|||
{ |
|||
// some code |
|||
} |
|||
|
|||
Attribute types |
|||
--------------- |
|||
|
|||
The annotation parser checks the given parameters using the phpdoc |
|||
annotation ``@var``, The data type could be validated using the ``@var`` |
|||
annotation on the annotation properties or using the ``@Attributes`` and |
|||
``@Attribute`` annotations. |
|||
|
|||
If the data type does not match you get an ``AnnotationException`` |
|||
|
|||
.. code-block:: php |
|||
|
|||
namespace MyCompany\Annotations; |
|||
|
|||
/** |
|||
* @Annotation |
|||
* @Target({"METHOD","PROPERTY"}) |
|||
*/ |
|||
class Bar |
|||
{ |
|||
/** @var mixed */ |
|||
public $mixed; |
|||
|
|||
/** @var boolean */ |
|||
public $boolean; |
|||
|
|||
/** @var bool */ |
|||
public $bool; |
|||
|
|||
/** @var float */ |
|||
public $float; |
|||
|
|||
/** @var string */ |
|||
public $string; |
|||
|
|||
/** @var integer */ |
|||
public $integer; |
|||
|
|||
/** @var array */ |
|||
public $array; |
|||
|
|||
/** @var SomeAnnotationClass */ |
|||
public $annotation; |
|||
|
|||
/** @var array<integer> */ |
|||
public $arrayOfIntegers; |
|||
|
|||
/** @var array<SomeAnnotationClass> */ |
|||
public $arrayOfAnnotations; |
|||
} |
|||
|
|||
/** |
|||
* @Annotation |
|||
* @Target({"METHOD","PROPERTY"}) |
|||
* @Attributes({ |
|||
* @Attribute("stringProperty", type = "string"), |
|||
* @Attribute("annotProperty", type = "SomeAnnotationClass"), |
|||
* }) |
|||
*/ |
|||
class Foo |
|||
{ |
|||
public function __construct(array $values) |
|||
{ |
|||
$this->stringProperty = $values['stringProperty']; |
|||
$this->annotProperty = $values['annotProperty']; |
|||
} |
|||
|
|||
// some code |
|||
} |
|||
|
|||
Annotation Required |
|||
------------------- |
|||
|
|||
``@Required`` indicates that the field must be specified when the |
|||
annotation is used. If it is not used you get an ``AnnotationException`` |
|||
stating that this value can not be null. |
|||
|
|||
Declaring a required field: |
|||
|
|||
.. code-block:: php |
|||
|
|||
/** |
|||
* @Annotation |
|||
* @Target("ALL") |
|||
*/ |
|||
class Foo |
|||
{ |
|||
/** @Required */ |
|||
public $requiredField; |
|||
} |
|||
|
|||
Usage: |
|||
|
|||
.. code-block:: php |
|||
|
|||
/** @Foo(requiredField="value") */ |
|||
public $direction; // Valid |
|||
|
|||
/** @Foo */ |
|||
public $direction; // Required field missing, throws an AnnotationException |
|||
|
|||
|
|||
Enumerated values |
|||
----------------- |
|||
|
|||
- An annotation property marked with ``@Enum`` is a field that accepts a |
|||
fixed set of scalar values. |
|||
- You should use ``@Enum`` fields any time you need to represent fixed |
|||
values. |
|||
- The annotation parser checks the given value and throws an |
|||
``AnnotationException`` if the value does not match. |
|||
|
|||
|
|||
Declaring an enumerated property: |
|||
|
|||
.. code-block:: php |
|||
|
|||
/** |
|||
* @Annotation |
|||
* @Target("ALL") |
|||
*/ |
|||
class Direction |
|||
{ |
|||
/** |
|||
* @Enum({"NORTH", "SOUTH", "EAST", "WEST"}) |
|||
*/ |
|||
public $value; |
|||
} |
|||
|
|||
Annotation usage: |
|||
|
|||
.. code-block:: php |
|||
|
|||
/** @Direction("NORTH") */ |
|||
public $direction; // Valid value |
|||
|
|||
/** @Direction("NORTHEAST") */ |
|||
public $direction; // Invalid value, throws an AnnotationException |
|||
|
|||
|
|||
Constants |
|||
--------- |
|||
|
|||
The use of constants and class constants is available on the annotations |
|||
parser. |
|||
|
|||
The following usages are allowed: |
|||
|
|||
.. code-block:: php |
|||
|
|||
namespace MyCompany\Entity; |
|||
|
|||
use MyCompany\Annotations\Foo; |
|||
use MyCompany\Annotations\Bar; |
|||
use MyCompany\Entity\SomeClass; |
|||
|
|||
/** |
|||
* @Foo(PHP_EOL) |
|||
* @Bar(Bar::FOO) |
|||
* @Foo({SomeClass::FOO, SomeClass::BAR}) |
|||
* @Bar({SomeClass::FOO_KEY = SomeClass::BAR_VALUE}) |
|||
*/ |
|||
class User |
|||
{ |
|||
} |
|||
|
|||
|
|||
Be careful with constants and the cache ! |
|||
|
|||
.. note:: |
|||
|
|||
The cached reader will not re-evaluate each time an annotation is |
|||
loaded from cache. When a constant is changed the cache must be |
|||
cleaned. |
|||
|
|||
|
|||
Usage |
|||
----- |
|||
|
|||
Using the library API is simple. Using the annotations described in the |
|||
previous section, you can now annotate other classes with your |
|||
annotations: |
|||
|
|||
.. code-block:: php |
|||
|
|||
namespace MyCompany\Entity; |
|||
|
|||
use MyCompany\Annotations\Foo; |
|||
use MyCompany\Annotations\Bar; |
|||
|
|||
/** |
|||
* @Foo(bar="foo") |
|||
* @Bar(foo="bar") |
|||
*/ |
|||
class User |
|||
{ |
|||
} |
|||
|
|||
Now we can write a script to get the annotations above: |
|||
|
|||
.. code-block:: php |
|||
|
|||
$reflClass = new ReflectionClass('MyCompany\Entity\User'); |
|||
$classAnnotations = $reader->getClassAnnotations($reflClass); |
|||
|
|||
foreach ($classAnnotations AS $annot) { |
|||
if ($annot instanceof \MyCompany\Annotations\Foo) { |
|||
echo $annot->bar; // prints "foo"; |
|||
} else if ($annot instanceof \MyCompany\Annotations\Bar) { |
|||
echo $annot->foo; // prints "bar"; |
|||
} |
|||
} |
|||
|
|||
You have a complete API for retrieving annotation class instances from a |
|||
class, property or method docblock: |
|||
|
|||
|
|||
Reader API |
|||
~~~~~~~~~~ |
|||
|
|||
Access all annotations of a class |
|||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|||
|
|||
.. code-block:: php |
|||
|
|||
public function getClassAnnotations(\ReflectionClass $class); |
|||
|
|||
Access one annotation of a class |
|||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|||
|
|||
.. code-block:: php |
|||
|
|||
public function getClassAnnotation(\ReflectionClass $class, $annotationName); |
|||
|
|||
Access all annotations of a method |
|||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|||
|
|||
.. code-block:: php |
|||
|
|||
public function getMethodAnnotations(\ReflectionMethod $method); |
|||
|
|||
Access one annotation of a method |
|||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|||
|
|||
.. code-block:: php |
|||
|
|||
public function getMethodAnnotation(\ReflectionMethod $method, $annotationName); |
|||
|
|||
Access all annotations of a property |
|||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|||
|
|||
.. code-block:: php |
|||
|
|||
public function getPropertyAnnotations(\ReflectionProperty $property); |
|||
|
|||
Access one annotation of a property |
|||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|||
|
|||
.. code-block:: php |
|||
|
|||
public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName); |
|||
|
|||
Access all annotations of a function |
|||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|||
|
|||
.. code-block:: php |
|||
|
|||
public function getFunctionAnnotations(\ReflectionFunction $property); |
|||
|
|||
Access one annotation of a function |
|||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|||
|
|||
.. code-block:: php |
|||
|
|||
public function getFunctionAnnotation(\ReflectionFunction $property, $annotationName); |
|||
@ -0,0 +1,101 @@ |
|||
Introduction |
|||
============ |
|||
|
|||
Doctrine Annotations allows to implement custom annotation |
|||
functionality for PHP classes and functions. |
|||
|
|||
.. code-block:: php |
|||
|
|||
class Foo |
|||
{ |
|||
/** |
|||
* @MyAnnotation(myProperty="value") |
|||
*/ |
|||
private $bar; |
|||
} |
|||
|
|||
Annotations aren't implemented in PHP itself which is why this component |
|||
offers a way to use the PHP doc-blocks as a place for the well known |
|||
annotation syntax using the ``@`` char. |
|||
|
|||
Annotations in Doctrine are used for the ORM configuration to build the |
|||
class mapping, but it can be used in other projects for other purposes |
|||
too. |
|||
|
|||
Installation |
|||
============ |
|||
|
|||
You can install the Annotation component with composer: |
|||
|
|||
.. code-block:: |
|||
|
|||
$ composer require doctrine/annotations |
|||
|
|||
Create an annotation class |
|||
========================== |
|||
|
|||
An annotation class is a representation of the later used annotation |
|||
configuration in classes. The annotation class of the previous example |
|||
looks like this: |
|||
|
|||
.. code-block:: php |
|||
|
|||
/** |
|||
* @Annotation |
|||
*/ |
|||
final class MyAnnotation |
|||
{ |
|||
public $myProperty; |
|||
} |
|||
|
|||
The annotation class is declared as an annotation by ``@Annotation``. |
|||
|
|||
:ref:`Read more about custom annotations. <custom>` |
|||
|
|||
Reading annotations |
|||
=================== |
|||
|
|||
The access to the annotations happens by reflection of the class or function |
|||
containing them. There are multiple reader-classes implementing the |
|||
``Doctrine\Common\Annotations\Reader`` interface, that can access the |
|||
annotations of a class. A common one is |
|||
``Doctrine\Common\Annotations\AnnotationReader``: |
|||
|
|||
.. code-block:: php |
|||
|
|||
use Doctrine\Common\Annotations\AnnotationReader; |
|||
use Doctrine\Common\Annotations\AnnotationRegistry; |
|||
|
|||
// Deprecated and will be removed in 2.0 but currently needed |
|||
AnnotationRegistry::registerLoader('class_exists'); |
|||
|
|||
$reflectionClass = new ReflectionClass(Foo::class); |
|||
$property = $reflectionClass->getProperty('bar'); |
|||
|
|||
$reader = new AnnotationReader(); |
|||
$myAnnotation = $reader->getPropertyAnnotation( |
|||
$property, |
|||
MyAnnotation::class |
|||
); |
|||
|
|||
echo $myAnnotation->myProperty; // result: "value" |
|||
|
|||
Note that ``AnnotationRegistry::registerLoader('class_exists')`` only works |
|||
if you already have an autoloader configured (i.e. composer autoloader). |
|||
Otherwise, :ref:`please take a look to the other annotation autoload mechanisms <annotations>`. |
|||
|
|||
A reader has multiple methods to access the annotations of a class or |
|||
function. |
|||
|
|||
:ref:`Read more about handling annotations. <annotations>` |
|||
|
|||
IDE Support |
|||
----------- |
|||
|
|||
Some IDEs already provide support for annotations: |
|||
|
|||
- Eclipse via the `Symfony2 Plugin <http://symfony.dubture.com/>`_ |
|||
- PhpStorm via the `PHP Annotations Plugin <https://plugins.jetbrains.com/plugin/7320-php-annotations>`_ or the `Symfony Plugin <https://plugins.jetbrains.com/plugin/7219-symfony-support>`_ |
|||
|
|||
.. _Read more about handling annotations.: annotations |
|||
.. _Read more about custom annotations.: custom |
|||
@ -0,0 +1,6 @@ |
|||
.. toctree:: |
|||
:depth: 3 |
|||
|
|||
index |
|||
annotations |
|||
custom |
|||
@ -0,0 +1,59 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations; |
|||
|
|||
use BadMethodCallException; |
|||
|
|||
use function sprintf; |
|||
|
|||
/** |
|||
* Annotations class. |
|||
*/ |
|||
class Annotation |
|||
{ |
|||
/** |
|||
* Value property. Common among all derived classes. |
|||
* |
|||
* @var mixed |
|||
*/ |
|||
public $value; |
|||
|
|||
/** |
|||
* @param array<string, mixed> $data Key-value for properties to be defined in this class. |
|||
*/ |
|||
final public function __construct(array $data) |
|||
{ |
|||
foreach ($data as $key => $value) { |
|||
$this->$key = $value; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Error handler for unknown property accessor in Annotation class. |
|||
* |
|||
* @param string $name Unknown property name. |
|||
* |
|||
* @throws BadMethodCallException |
|||
*/ |
|||
public function __get($name) |
|||
{ |
|||
throw new BadMethodCallException( |
|||
sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Error handler for unknown property mutator in Annotation class. |
|||
* |
|||
* @param string $name Unknown property name. |
|||
* @param mixed $value Property value. |
|||
* |
|||
* @throws BadMethodCallException |
|||
*/ |
|||
public function __set($name, $value) |
|||
{ |
|||
throw new BadMethodCallException( |
|||
sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations\Annotation; |
|||
|
|||
/** |
|||
* Annotation that can be used to signal to the parser |
|||
* to check the attribute type during the parsing process. |
|||
* |
|||
* @Annotation |
|||
*/ |
|||
final class Attribute |
|||
{ |
|||
/** @var string */ |
|||
public $name; |
|||
|
|||
/** @var string */ |
|||
public $type; |
|||
|
|||
/** @var bool */ |
|||
public $required = false; |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations\Annotation; |
|||
|
|||
/** |
|||
* Annotation that can be used to signal to the parser |
|||
* to check the types of all declared attributes during the parsing process. |
|||
* |
|||
* @Annotation |
|||
*/ |
|||
final class Attributes |
|||
{ |
|||
/** @var array<Attribute> */ |
|||
public $value; |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations\Annotation; |
|||
|
|||
use InvalidArgumentException; |
|||
|
|||
use function get_class; |
|||
use function gettype; |
|||
use function in_array; |
|||
use function is_object; |
|||
use function is_scalar; |
|||
use function sprintf; |
|||
|
|||
/** |
|||
* Annotation that can be used to signal to the parser |
|||
* to check the available values during the parsing process. |
|||
* |
|||
* @Annotation |
|||
* @Attributes({ |
|||
* @Attribute("value", required = true, type = "array"), |
|||
* @Attribute("literal", required = false, type = "array") |
|||
* }) |
|||
*/ |
|||
final class Enum |
|||
{ |
|||
/** @phpstan-var list<scalar> */ |
|||
public $value; |
|||
|
|||
/** |
|||
* Literal target declaration. |
|||
* |
|||
* @var mixed[] |
|||
*/ |
|||
public $literal; |
|||
|
|||
/** |
|||
* @throws InvalidArgumentException |
|||
* |
|||
* @phpstan-param array{literal?: mixed[], value: list<scalar>} $values |
|||
*/ |
|||
public function __construct(array $values) |
|||
{ |
|||
if (! isset($values['literal'])) { |
|||
$values['literal'] = []; |
|||
} |
|||
|
|||
foreach ($values['value'] as $var) { |
|||
if (! is_scalar($var)) { |
|||
throw new InvalidArgumentException(sprintf( |
|||
'@Enum supports only scalar values "%s" given.', |
|||
is_object($var) ? get_class($var) : gettype($var) |
|||
)); |
|||
} |
|||
} |
|||
|
|||
foreach ($values['literal'] as $key => $var) { |
|||
if (! in_array($key, $values['value'])) { |
|||
throw new InvalidArgumentException(sprintf( |
|||
'Undefined enumerator value "%s" for literal "%s".', |
|||
$key, |
|||
$var |
|||
)); |
|||
} |
|||
} |
|||
|
|||
$this->value = $values['value']; |
|||
$this->literal = $values['literal']; |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations\Annotation; |
|||
|
|||
use RuntimeException; |
|||
|
|||
use function is_array; |
|||
use function is_string; |
|||
use function json_encode; |
|||
use function sprintf; |
|||
|
|||
/** |
|||
* Annotation that can be used to signal to the parser to ignore specific |
|||
* annotations during the parsing process. |
|||
* |
|||
* @Annotation |
|||
*/ |
|||
final class IgnoreAnnotation |
|||
{ |
|||
/** @phpstan-var list<string> */ |
|||
public $names; |
|||
|
|||
/** |
|||
* @throws RuntimeException |
|||
* |
|||
* @phpstan-param array{value: string|list<string>} $values |
|||
*/ |
|||
public function __construct(array $values) |
|||
{ |
|||
if (is_string($values['value'])) { |
|||
$values['value'] = [$values['value']]; |
|||
} |
|||
|
|||
if (! is_array($values['value'])) { |
|||
throw new RuntimeException(sprintf( |
|||
'@IgnoreAnnotation expects either a string name, or an array of strings, but got %s.', |
|||
json_encode($values['value']) |
|||
)); |
|||
} |
|||
|
|||
$this->names = $values['value']; |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations\Annotation; |
|||
|
|||
/** |
|||
* Annotation that indicates that the annotated class should be constructed with a named argument call. |
|||
* |
|||
* @Annotation |
|||
* @Target("CLASS") |
|||
*/ |
|||
final class NamedArgumentConstructor |
|||
{ |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations\Annotation; |
|||
|
|||
/** |
|||
* Annotation that can be used to signal to the parser |
|||
* to check if that attribute is required during the parsing process. |
|||
* |
|||
* @Annotation |
|||
*/ |
|||
final class Required |
|||
{ |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations\Annotation; |
|||
|
|||
use InvalidArgumentException; |
|||
|
|||
use function array_keys; |
|||
use function get_class; |
|||
use function gettype; |
|||
use function implode; |
|||
use function is_array; |
|||
use function is_object; |
|||
use function is_string; |
|||
use function sprintf; |
|||
|
|||
/** |
|||
* Annotation that can be used to signal to the parser |
|||
* to check the annotation target during the parsing process. |
|||
* |
|||
* @Annotation |
|||
*/ |
|||
final class Target |
|||
{ |
|||
public const TARGET_CLASS = 1; |
|||
public const TARGET_METHOD = 2; |
|||
public const TARGET_PROPERTY = 4; |
|||
public const TARGET_ANNOTATION = 8; |
|||
public const TARGET_FUNCTION = 16; |
|||
public const TARGET_ALL = 31; |
|||
|
|||
/** @var array<string, int> */ |
|||
private static $map = [ |
|||
'ALL' => self::TARGET_ALL, |
|||
'CLASS' => self::TARGET_CLASS, |
|||
'METHOD' => self::TARGET_METHOD, |
|||
'PROPERTY' => self::TARGET_PROPERTY, |
|||
'FUNCTION' => self::TARGET_FUNCTION, |
|||
'ANNOTATION' => self::TARGET_ANNOTATION, |
|||
]; |
|||
|
|||
/** @phpstan-var list<string> */ |
|||
public $value; |
|||
|
|||
/** |
|||
* Targets as bitmask. |
|||
* |
|||
* @var int |
|||
*/ |
|||
public $targets; |
|||
|
|||
/** |
|||
* Literal target declaration. |
|||
* |
|||
* @var string |
|||
*/ |
|||
public $literal; |
|||
|
|||
/** |
|||
* @throws InvalidArgumentException |
|||
* |
|||
* @phpstan-param array{value?: string|list<string>} $values |
|||
*/ |
|||
public function __construct(array $values) |
|||
{ |
|||
if (! isset($values['value'])) { |
|||
$values['value'] = null; |
|||
} |
|||
|
|||
if (is_string($values['value'])) { |
|||
$values['value'] = [$values['value']]; |
|||
} |
|||
|
|||
if (! is_array($values['value'])) { |
|||
throw new InvalidArgumentException( |
|||
sprintf( |
|||
'@Target expects either a string value, or an array of strings, "%s" given.', |
|||
is_object($values['value']) ? get_class($values['value']) : gettype($values['value']) |
|||
) |
|||
); |
|||
} |
|||
|
|||
$bitmask = 0; |
|||
foreach ($values['value'] as $literal) { |
|||
if (! isset(self::$map[$literal])) { |
|||
throw new InvalidArgumentException( |
|||
sprintf( |
|||
'Invalid Target "%s". Available targets: [%s]', |
|||
$literal, |
|||
implode(', ', array_keys(self::$map)) |
|||
) |
|||
); |
|||
} |
|||
|
|||
$bitmask |= self::$map[$literal]; |
|||
} |
|||
|
|||
$this->targets = $bitmask; |
|||
$this->value = $values['value']; |
|||
$this->literal = implode(', ', $this->value); |
|||
} |
|||
} |
|||
@ -0,0 +1,171 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations; |
|||
|
|||
use Exception; |
|||
|
|||
use function get_class; |
|||
use function gettype; |
|||
use function implode; |
|||
use function is_object; |
|||
use function sprintf; |
|||
|
|||
/** |
|||
* Description of AnnotationException |
|||
*/ |
|||
class AnnotationException extends Exception |
|||
{ |
|||
/** |
|||
* Creates a new AnnotationException describing a Syntax error. |
|||
* |
|||
* @param string $message Exception message |
|||
* |
|||
* @return AnnotationException |
|||
*/ |
|||
public static function syntaxError($message) |
|||
{ |
|||
return new self('[Syntax Error] ' . $message); |
|||
} |
|||
|
|||
/** |
|||
* Creates a new AnnotationException describing a Semantical error. |
|||
* |
|||
* @param string $message Exception message |
|||
* |
|||
* @return AnnotationException |
|||
*/ |
|||
public static function semanticalError($message) |
|||
{ |
|||
return new self('[Semantical Error] ' . $message); |
|||
} |
|||
|
|||
/** |
|||
* Creates a new AnnotationException describing an error which occurred during |
|||
* the creation of the annotation. |
|||
* |
|||
* @param string $message |
|||
* |
|||
* @return AnnotationException |
|||
*/ |
|||
public static function creationError($message) |
|||
{ |
|||
return new self('[Creation Error] ' . $message); |
|||
} |
|||
|
|||
/** |
|||
* Creates a new AnnotationException describing a type error. |
|||
* |
|||
* @param string $message |
|||
* |
|||
* @return AnnotationException |
|||
*/ |
|||
public static function typeError($message) |
|||
{ |
|||
return new self('[Type Error] ' . $message); |
|||
} |
|||
|
|||
/** |
|||
* Creates a new AnnotationException describing a constant semantical error. |
|||
* |
|||
* @param string $identifier |
|||
* @param string $context |
|||
* |
|||
* @return AnnotationException |
|||
*/ |
|||
public static function semanticalErrorConstants($identifier, $context = null) |
|||
{ |
|||
return self::semanticalError(sprintf( |
|||
"Couldn't find constant %s%s.", |
|||
$identifier, |
|||
$context ? ', ' . $context : '' |
|||
)); |
|||
} |
|||
|
|||
/** |
|||
* Creates a new AnnotationException describing an type error of an attribute. |
|||
* |
|||
* @param string $attributeName |
|||
* @param string $annotationName |
|||
* @param string $context |
|||
* @param string $expected |
|||
* @param mixed $actual |
|||
* |
|||
* @return AnnotationException |
|||
*/ |
|||
public static function attributeTypeError($attributeName, $annotationName, $context, $expected, $actual) |
|||
{ |
|||
return self::typeError(sprintf( |
|||
'Attribute "%s" of @%s declared on %s expects %s, but got %s.', |
|||
$attributeName, |
|||
$annotationName, |
|||
$context, |
|||
$expected, |
|||
is_object($actual) ? 'an instance of ' . get_class($actual) : gettype($actual) |
|||
)); |
|||
} |
|||
|
|||
/** |
|||
* Creates a new AnnotationException describing an required error of an attribute. |
|||
* |
|||
* @param string $attributeName |
|||
* @param string $annotationName |
|||
* @param string $context |
|||
* @param string $expected |
|||
* |
|||
* @return AnnotationException |
|||
*/ |
|||
public static function requiredError($attributeName, $annotationName, $context, $expected) |
|||
{ |
|||
return self::typeError(sprintf( |
|||
'Attribute "%s" of @%s declared on %s expects %s. This value should not be null.', |
|||
$attributeName, |
|||
$annotationName, |
|||
$context, |
|||
$expected |
|||
)); |
|||
} |
|||
|
|||
/** |
|||
* Creates a new AnnotationException describing a invalid enummerator. |
|||
* |
|||
* @param string $attributeName |
|||
* @param string $annotationName |
|||
* @param string $context |
|||
* @param mixed $given |
|||
* |
|||
* @return AnnotationException |
|||
* |
|||
* @phpstan-param list<string> $available |
|||
*/ |
|||
public static function enumeratorError($attributeName, $annotationName, $context, $available, $given) |
|||
{ |
|||
return new self(sprintf( |
|||
'[Enum Error] Attribute "%s" of @%s declared on %s accepts only [%s], but got %s.', |
|||
$attributeName, |
|||
$annotationName, |
|||
$context, |
|||
implode(', ', $available), |
|||
is_object($given) ? get_class($given) : $given |
|||
)); |
|||
} |
|||
|
|||
/** |
|||
* @return AnnotationException |
|||
*/ |
|||
public static function optimizerPlusSaveComments() |
|||
{ |
|||
return new self( |
|||
'You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1.' |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* @return AnnotationException |
|||
*/ |
|||
public static function optimizerPlusLoadComments() |
|||
{ |
|||
return new self( |
|||
'You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1.' |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,389 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations; |
|||
|
|||
use Doctrine\Common\Annotations\Annotation\IgnoreAnnotation; |
|||
use Doctrine\Common\Annotations\Annotation\Target; |
|||
use ReflectionClass; |
|||
use ReflectionFunction; |
|||
use ReflectionMethod; |
|||
use ReflectionProperty; |
|||
|
|||
use function array_merge; |
|||
use function class_exists; |
|||
use function extension_loaded; |
|||
use function ini_get; |
|||
|
|||
/** |
|||
* A reader for docblock annotations. |
|||
*/ |
|||
class AnnotationReader implements Reader |
|||
{ |
|||
/** |
|||
* Global map for imports. |
|||
* |
|||
* @var array<string, class-string> |
|||
*/ |
|||
private static $globalImports = [ |
|||
'ignoreannotation' => Annotation\IgnoreAnnotation::class, |
|||
]; |
|||
|
|||
/** |
|||
* A list with annotations that are not causing exceptions when not resolved to an annotation class. |
|||
* |
|||
* The names are case sensitive. |
|||
* |
|||
* @var array<string, true> |
|||
*/ |
|||
private static $globalIgnoredNames = ImplicitlyIgnoredAnnotationNames::LIST; |
|||
|
|||
/** |
|||
* A list with annotations that are not causing exceptions when not resolved to an annotation class. |
|||
* |
|||
* The names are case sensitive. |
|||
* |
|||
* @var array<string, true> |
|||
*/ |
|||
private static $globalIgnoredNamespaces = []; |
|||
|
|||
/** |
|||
* Add a new annotation to the globally ignored annotation names with regard to exception handling. |
|||
* |
|||
* @param string $name |
|||
*/ |
|||
public static function addGlobalIgnoredName($name) |
|||
{ |
|||
self::$globalIgnoredNames[$name] = true; |
|||
} |
|||
|
|||
/** |
|||
* Add a new annotation to the globally ignored annotation namespaces with regard to exception handling. |
|||
* |
|||
* @param string $namespace |
|||
*/ |
|||
public static function addGlobalIgnoredNamespace($namespace) |
|||
{ |
|||
self::$globalIgnoredNamespaces[$namespace] = true; |
|||
} |
|||
|
|||
/** |
|||
* Annotations parser. |
|||
* |
|||
* @var DocParser |
|||
*/ |
|||
private $parser; |
|||
|
|||
/** |
|||
* Annotations parser used to collect parsing metadata. |
|||
* |
|||
* @var DocParser |
|||
*/ |
|||
private $preParser; |
|||
|
|||
/** |
|||
* PHP parser used to collect imports. |
|||
* |
|||
* @var PhpParser |
|||
*/ |
|||
private $phpParser; |
|||
|
|||
/** |
|||
* In-memory cache mechanism to store imported annotations per class. |
|||
* |
|||
* @psalm-var array<'class'|'function', array<string, array<string, class-string>>> |
|||
*/ |
|||
private $imports = []; |
|||
|
|||
/** |
|||
* In-memory cache mechanism to store ignored annotations per class. |
|||
* |
|||
* @psalm-var array<'class'|'function', array<string, array<string, true>>> |
|||
*/ |
|||
private $ignoredAnnotationNames = []; |
|||
|
|||
/** |
|||
* Initializes a new AnnotationReader. |
|||
* |
|||
* @throws AnnotationException |
|||
*/ |
|||
public function __construct(?DocParser $parser = null) |
|||
{ |
|||
if ( |
|||
extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === '0' || |
|||
ini_get('opcache.save_comments') === '0') |
|||
) { |
|||
throw AnnotationException::optimizerPlusSaveComments(); |
|||
} |
|||
|
|||
if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') === 0) { |
|||
throw AnnotationException::optimizerPlusSaveComments(); |
|||
} |
|||
|
|||
// Make sure that the IgnoreAnnotation annotation is loaded |
|||
class_exists(IgnoreAnnotation::class); |
|||
|
|||
$this->parser = $parser ?: new DocParser(); |
|||
|
|||
$this->preParser = new DocParser(); |
|||
|
|||
$this->preParser->setImports(self::$globalImports); |
|||
$this->preParser->setIgnoreNotImportedAnnotations(true); |
|||
$this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames); |
|||
|
|||
$this->phpParser = new PhpParser(); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getClassAnnotations(ReflectionClass $class) |
|||
{ |
|||
$this->parser->setTarget(Target::TARGET_CLASS); |
|||
$this->parser->setImports($this->getImports($class)); |
|||
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); |
|||
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); |
|||
|
|||
return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getClassAnnotation(ReflectionClass $class, $annotationName) |
|||
{ |
|||
$annotations = $this->getClassAnnotations($class); |
|||
|
|||
foreach ($annotations as $annotation) { |
|||
if ($annotation instanceof $annotationName) { |
|||
return $annotation; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getPropertyAnnotations(ReflectionProperty $property) |
|||
{ |
|||
$class = $property->getDeclaringClass(); |
|||
$context = 'property ' . $class->getName() . '::$' . $property->getName(); |
|||
|
|||
$this->parser->setTarget(Target::TARGET_PROPERTY); |
|||
$this->parser->setImports($this->getPropertyImports($property)); |
|||
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); |
|||
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); |
|||
|
|||
return $this->parser->parse($property->getDocComment(), $context); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) |
|||
{ |
|||
$annotations = $this->getPropertyAnnotations($property); |
|||
|
|||
foreach ($annotations as $annotation) { |
|||
if ($annotation instanceof $annotationName) { |
|||
return $annotation; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getMethodAnnotations(ReflectionMethod $method) |
|||
{ |
|||
$class = $method->getDeclaringClass(); |
|||
$context = 'method ' . $class->getName() . '::' . $method->getName() . '()'; |
|||
|
|||
$this->parser->setTarget(Target::TARGET_METHOD); |
|||
$this->parser->setImports($this->getMethodImports($method)); |
|||
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); |
|||
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); |
|||
|
|||
return $this->parser->parse($method->getDocComment(), $context); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getMethodAnnotation(ReflectionMethod $method, $annotationName) |
|||
{ |
|||
$annotations = $this->getMethodAnnotations($method); |
|||
|
|||
foreach ($annotations as $annotation) { |
|||
if ($annotation instanceof $annotationName) { |
|||
return $annotation; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Gets the annotations applied to a function. |
|||
* |
|||
* @phpstan-return list<object> An array of Annotations. |
|||
*/ |
|||
public function getFunctionAnnotations(ReflectionFunction $function): array |
|||
{ |
|||
$context = 'function ' . $function->getName(); |
|||
|
|||
$this->parser->setTarget(Target::TARGET_FUNCTION); |
|||
$this->parser->setImports($this->getImports($function)); |
|||
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($function)); |
|||
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); |
|||
|
|||
return $this->parser->parse($function->getDocComment(), $context); |
|||
} |
|||
|
|||
/** |
|||
* Gets a function annotation. |
|||
* |
|||
* @return object|null The Annotation or NULL, if the requested annotation does not exist. |
|||
*/ |
|||
public function getFunctionAnnotation(ReflectionFunction $function, string $annotationName) |
|||
{ |
|||
$annotations = $this->getFunctionAnnotations($function); |
|||
|
|||
foreach ($annotations as $annotation) { |
|||
if ($annotation instanceof $annotationName) { |
|||
return $annotation; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Returns the ignored annotations for the given class or function. |
|||
* |
|||
* @param ReflectionClass|ReflectionFunction $reflection |
|||
* |
|||
* @return array<string, true> |
|||
*/ |
|||
private function getIgnoredAnnotationNames($reflection): array |
|||
{ |
|||
$type = $reflection instanceof ReflectionClass ? 'class' : 'function'; |
|||
$name = $reflection->getName(); |
|||
|
|||
if (isset($this->ignoredAnnotationNames[$type][$name])) { |
|||
return $this->ignoredAnnotationNames[$type][$name]; |
|||
} |
|||
|
|||
$this->collectParsingMetadata($reflection); |
|||
|
|||
return $this->ignoredAnnotationNames[$type][$name]; |
|||
} |
|||
|
|||
/** |
|||
* Retrieves imports for a class or a function. |
|||
* |
|||
* @param ReflectionClass|ReflectionFunction $reflection |
|||
* |
|||
* @return array<string, class-string> |
|||
*/ |
|||
private function getImports($reflection): array |
|||
{ |
|||
$type = $reflection instanceof ReflectionClass ? 'class' : 'function'; |
|||
$name = $reflection->getName(); |
|||
|
|||
if (isset($this->imports[$type][$name])) { |
|||
return $this->imports[$type][$name]; |
|||
} |
|||
|
|||
$this->collectParsingMetadata($reflection); |
|||
|
|||
return $this->imports[$type][$name]; |
|||
} |
|||
|
|||
/** |
|||
* Retrieves imports for methods. |
|||
* |
|||
* @return array<string, class-string> |
|||
*/ |
|||
private function getMethodImports(ReflectionMethod $method) |
|||
{ |
|||
$class = $method->getDeclaringClass(); |
|||
$classImports = $this->getImports($class); |
|||
|
|||
$traitImports = []; |
|||
|
|||
foreach ($class->getTraits() as $trait) { |
|||
if ( |
|||
! $trait->hasMethod($method->getName()) |
|||
|| $trait->getFileName() !== $method->getFileName() |
|||
) { |
|||
continue; |
|||
} |
|||
|
|||
$traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait)); |
|||
} |
|||
|
|||
return array_merge($classImports, $traitImports); |
|||
} |
|||
|
|||
/** |
|||
* Retrieves imports for properties. |
|||
* |
|||
* @return array<string, class-string> |
|||
*/ |
|||
private function getPropertyImports(ReflectionProperty $property) |
|||
{ |
|||
$class = $property->getDeclaringClass(); |
|||
$classImports = $this->getImports($class); |
|||
|
|||
$traitImports = []; |
|||
|
|||
foreach ($class->getTraits() as $trait) { |
|||
if (! $trait->hasProperty($property->getName())) { |
|||
continue; |
|||
} |
|||
|
|||
$traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait)); |
|||
} |
|||
|
|||
return array_merge($classImports, $traitImports); |
|||
} |
|||
|
|||
/** |
|||
* Collects parsing metadata for a given class or function. |
|||
* |
|||
* @param ReflectionClass|ReflectionFunction $reflection |
|||
*/ |
|||
private function collectParsingMetadata($reflection): void |
|||
{ |
|||
$type = $reflection instanceof ReflectionClass ? 'class' : 'function'; |
|||
$name = $reflection->getName(); |
|||
|
|||
$ignoredAnnotationNames = self::$globalIgnoredNames; |
|||
$annotations = $this->preParser->parse($reflection->getDocComment(), $type . ' ' . $name); |
|||
|
|||
foreach ($annotations as $annotation) { |
|||
if (! ($annotation instanceof IgnoreAnnotation)) { |
|||
continue; |
|||
} |
|||
|
|||
foreach ($annotation->names as $annot) { |
|||
$ignoredAnnotationNames[$annot] = true; |
|||
} |
|||
} |
|||
|
|||
$this->imports[$type][$name] = array_merge( |
|||
self::$globalImports, |
|||
$this->phpParser->parseUseStatements($reflection), |
|||
[ |
|||
'__NAMESPACE__' => $reflection->getNamespaceName(), |
|||
'self' => $name, |
|||
] |
|||
); |
|||
|
|||
$this->ignoredAnnotationNames[$type][$name] = $ignoredAnnotationNames; |
|||
} |
|||
} |
|||
@ -0,0 +1,190 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations; |
|||
|
|||
use function array_key_exists; |
|||
use function array_merge; |
|||
use function class_exists; |
|||
use function in_array; |
|||
use function is_file; |
|||
use function str_replace; |
|||
use function stream_resolve_include_path; |
|||
use function strpos; |
|||
|
|||
use const DIRECTORY_SEPARATOR; |
|||
|
|||
final class AnnotationRegistry |
|||
{ |
|||
/** |
|||
* A map of namespaces to use for autoloading purposes based on a PSR-0 convention. |
|||
* |
|||
* Contains the namespace as key and an array of directories as value. If the value is NULL |
|||
* the include path is used for checking for the corresponding file. |
|||
* |
|||
* This autoloading mechanism does not utilize the PHP autoloading but implements autoloading on its own. |
|||
* |
|||
* @var string[][]|string[]|null[] |
|||
*/ |
|||
private static $autoloadNamespaces = []; |
|||
|
|||
/** |
|||
* A map of autoloader callables. |
|||
* |
|||
* @var callable[] |
|||
*/ |
|||
private static $loaders = []; |
|||
|
|||
/** |
|||
* An array of classes which cannot be found |
|||
* |
|||
* @var null[] indexed by class name |
|||
*/ |
|||
private static $failedToAutoload = []; |
|||
|
|||
/** |
|||
* Whenever registerFile() was used. Disables use of standard autoloader. |
|||
* |
|||
* @var bool |
|||
*/ |
|||
private static $registerFileUsed = false; |
|||
|
|||
public static function reset(): void |
|||
{ |
|||
self::$autoloadNamespaces = []; |
|||
self::$loaders = []; |
|||
self::$failedToAutoload = []; |
|||
self::$registerFileUsed = false; |
|||
} |
|||
|
|||
/** |
|||
* Registers file. |
|||
* |
|||
* @deprecated This method is deprecated and will be removed in |
|||
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. |
|||
*/ |
|||
public static function registerFile(string $file): void |
|||
{ |
|||
self::$registerFileUsed = true; |
|||
|
|||
require_once $file; |
|||
} |
|||
|
|||
/** |
|||
* Adds a namespace with one or many directories to look for files or null for the include path. |
|||
* |
|||
* Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. |
|||
* |
|||
* @deprecated This method is deprecated and will be removed in |
|||
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. |
|||
* |
|||
* @phpstan-param string|list<string>|null $dirs |
|||
*/ |
|||
public static function registerAutoloadNamespace(string $namespace, $dirs = null): void |
|||
{ |
|||
self::$autoloadNamespaces[$namespace] = $dirs; |
|||
} |
|||
|
|||
/** |
|||
* Registers multiple namespaces. |
|||
* |
|||
* Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. |
|||
* |
|||
* @deprecated This method is deprecated and will be removed in |
|||
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. |
|||
* |
|||
* @param string[][]|string[]|null[] $namespaces indexed by namespace name |
|||
*/ |
|||
public static function registerAutoloadNamespaces(array $namespaces): void |
|||
{ |
|||
self::$autoloadNamespaces = array_merge(self::$autoloadNamespaces, $namespaces); |
|||
} |
|||
|
|||
/** |
|||
* Registers an autoloading callable for annotations, much like spl_autoload_register(). |
|||
* |
|||
* NOTE: These class loaders HAVE to be silent when a class was not found! |
|||
* IMPORTANT: Loaders have to return true if they loaded a class that could contain the searched annotation class. |
|||
* |
|||
* @deprecated This method is deprecated and will be removed in |
|||
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. |
|||
*/ |
|||
public static function registerLoader(callable $callable): void |
|||
{ |
|||
// Reset our static cache now that we have a new loader to work with |
|||
self::$failedToAutoload = []; |
|||
self::$loaders[] = $callable; |
|||
} |
|||
|
|||
/** |
|||
* Registers an autoloading callable for annotations, if it is not already registered |
|||
* |
|||
* @deprecated This method is deprecated and will be removed in |
|||
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. |
|||
*/ |
|||
public static function registerUniqueLoader(callable $callable): void |
|||
{ |
|||
if (in_array($callable, self::$loaders, true)) { |
|||
return; |
|||
} |
|||
|
|||
self::registerLoader($callable); |
|||
} |
|||
|
|||
/** |
|||
* Autoloads an annotation class silently. |
|||
*/ |
|||
public static function loadAnnotationClass(string $class): bool |
|||
{ |
|||
if (class_exists($class, false)) { |
|||
return true; |
|||
} |
|||
|
|||
if (array_key_exists($class, self::$failedToAutoload)) { |
|||
return false; |
|||
} |
|||
|
|||
foreach (self::$autoloadNamespaces as $namespace => $dirs) { |
|||
if (strpos($class, $namespace) !== 0) { |
|||
continue; |
|||
} |
|||
|
|||
$file = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; |
|||
|
|||
if ($dirs === null) { |
|||
$path = stream_resolve_include_path($file); |
|||
if ($path) { |
|||
require $path; |
|||
|
|||
return true; |
|||
} |
|||
} else { |
|||
foreach ((array) $dirs as $dir) { |
|||
if (is_file($dir . DIRECTORY_SEPARATOR . $file)) { |
|||
require $dir . DIRECTORY_SEPARATOR . $file; |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
foreach (self::$loaders as $loader) { |
|||
if ($loader($class) === true) { |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
if ( |
|||
self::$loaders === [] && |
|||
self::$autoloadNamespaces === [] && |
|||
self::$registerFileUsed === false && |
|||
class_exists($class) |
|||
) { |
|||
return true; |
|||
} |
|||
|
|||
self::$failedToAutoload[$class] = null; |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
@ -0,0 +1,264 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations; |
|||
|
|||
use Doctrine\Common\Cache\Cache; |
|||
use ReflectionClass; |
|||
use ReflectionMethod; |
|||
use ReflectionProperty; |
|||
|
|||
use function array_map; |
|||
use function array_merge; |
|||
use function assert; |
|||
use function filemtime; |
|||
use function max; |
|||
use function time; |
|||
|
|||
/** |
|||
* A cache aware annotation reader. |
|||
*/ |
|||
final class CachedReader implements Reader |
|||
{ |
|||
/** @var Reader */ |
|||
private $delegate; |
|||
|
|||
/** @var Cache */ |
|||
private $cache; |
|||
|
|||
/** @var bool */ |
|||
private $debug; |
|||
|
|||
/** @var array<string, array<object>> */ |
|||
private $loadedAnnotations = []; |
|||
|
|||
/** @var int[] */ |
|||
private $loadedFilemtimes = []; |
|||
|
|||
/** |
|||
* @param bool $debug |
|||
*/ |
|||
public function __construct(Reader $reader, Cache $cache, $debug = false) |
|||
{ |
|||
$this->delegate = $reader; |
|||
$this->cache = $cache; |
|||
$this->debug = (bool) $debug; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getClassAnnotations(ReflectionClass $class) |
|||
{ |
|||
$cacheKey = $class->getName(); |
|||
|
|||
if (isset($this->loadedAnnotations[$cacheKey])) { |
|||
return $this->loadedAnnotations[$cacheKey]; |
|||
} |
|||
|
|||
$annots = $this->fetchFromCache($cacheKey, $class); |
|||
if ($annots === false) { |
|||
$annots = $this->delegate->getClassAnnotations($class); |
|||
$this->saveToCache($cacheKey, $annots); |
|||
} |
|||
|
|||
return $this->loadedAnnotations[$cacheKey] = $annots; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getClassAnnotation(ReflectionClass $class, $annotationName) |
|||
{ |
|||
foreach ($this->getClassAnnotations($class) as $annot) { |
|||
if ($annot instanceof $annotationName) { |
|||
return $annot; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getPropertyAnnotations(ReflectionProperty $property) |
|||
{ |
|||
$class = $property->getDeclaringClass(); |
|||
$cacheKey = $class->getName() . '$' . $property->getName(); |
|||
|
|||
if (isset($this->loadedAnnotations[$cacheKey])) { |
|||
return $this->loadedAnnotations[$cacheKey]; |
|||
} |
|||
|
|||
$annots = $this->fetchFromCache($cacheKey, $class); |
|||
if ($annots === false) { |
|||
$annots = $this->delegate->getPropertyAnnotations($property); |
|||
$this->saveToCache($cacheKey, $annots); |
|||
} |
|||
|
|||
return $this->loadedAnnotations[$cacheKey] = $annots; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) |
|||
{ |
|||
foreach ($this->getPropertyAnnotations($property) as $annot) { |
|||
if ($annot instanceof $annotationName) { |
|||
return $annot; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getMethodAnnotations(ReflectionMethod $method) |
|||
{ |
|||
$class = $method->getDeclaringClass(); |
|||
$cacheKey = $class->getName() . '#' . $method->getName(); |
|||
|
|||
if (isset($this->loadedAnnotations[$cacheKey])) { |
|||
return $this->loadedAnnotations[$cacheKey]; |
|||
} |
|||
|
|||
$annots = $this->fetchFromCache($cacheKey, $class); |
|||
if ($annots === false) { |
|||
$annots = $this->delegate->getMethodAnnotations($method); |
|||
$this->saveToCache($cacheKey, $annots); |
|||
} |
|||
|
|||
return $this->loadedAnnotations[$cacheKey] = $annots; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getMethodAnnotation(ReflectionMethod $method, $annotationName) |
|||
{ |
|||
foreach ($this->getMethodAnnotations($method) as $annot) { |
|||
if ($annot instanceof $annotationName) { |
|||
return $annot; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Clears loaded annotations. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function clearLoadedAnnotations() |
|||
{ |
|||
$this->loadedAnnotations = []; |
|||
$this->loadedFilemtimes = []; |
|||
} |
|||
|
|||
/** |
|||
* Fetches a value from the cache. |
|||
* |
|||
* @param string $cacheKey The cache key. |
|||
* |
|||
* @return mixed The cached value or false when the value is not in cache. |
|||
*/ |
|||
private function fetchFromCache($cacheKey, ReflectionClass $class) |
|||
{ |
|||
$data = $this->cache->fetch($cacheKey); |
|||
if ($data !== false) { |
|||
if (! $this->debug || $this->isCacheFresh($cacheKey, $class)) { |
|||
return $data; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* Saves a value to the cache. |
|||
* |
|||
* @param string $cacheKey The cache key. |
|||
* @param mixed $value The value. |
|||
* |
|||
* @return void |
|||
*/ |
|||
private function saveToCache($cacheKey, $value) |
|||
{ |
|||
$this->cache->save($cacheKey, $value); |
|||
if (! $this->debug) { |
|||
return; |
|||
} |
|||
|
|||
$this->cache->save('[C]' . $cacheKey, time()); |
|||
} |
|||
|
|||
/** |
|||
* Checks if the cache is fresh. |
|||
* |
|||
* @param string $cacheKey |
|||
* |
|||
* @return bool |
|||
*/ |
|||
private function isCacheFresh($cacheKey, ReflectionClass $class) |
|||
{ |
|||
$lastModification = $this->getLastModification($class); |
|||
if ($lastModification === 0) { |
|||
return true; |
|||
} |
|||
|
|||
return $this->cache->fetch('[C]' . $cacheKey) >= $lastModification; |
|||
} |
|||
|
|||
/** |
|||
* Returns the time the class was last modified, testing traits and parents |
|||
*/ |
|||
private function getLastModification(ReflectionClass $class): int |
|||
{ |
|||
$filename = $class->getFileName(); |
|||
|
|||
if (isset($this->loadedFilemtimes[$filename])) { |
|||
return $this->loadedFilemtimes[$filename]; |
|||
} |
|||
|
|||
$parent = $class->getParentClass(); |
|||
|
|||
$lastModification = max(array_merge( |
|||
[$filename ? filemtime($filename) : 0], |
|||
array_map(function (ReflectionClass $reflectionTrait): int { |
|||
return $this->getTraitLastModificationTime($reflectionTrait); |
|||
}, $class->getTraits()), |
|||
array_map(function (ReflectionClass $class): int { |
|||
return $this->getLastModification($class); |
|||
}, $class->getInterfaces()), |
|||
$parent ? [$this->getLastModification($parent)] : [] |
|||
)); |
|||
|
|||
assert($lastModification !== false); |
|||
|
|||
return $this->loadedFilemtimes[$filename] = $lastModification; |
|||
} |
|||
|
|||
private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int |
|||
{ |
|||
$fileName = $reflectionTrait->getFileName(); |
|||
|
|||
if (isset($this->loadedFilemtimes[$fileName])) { |
|||
return $this->loadedFilemtimes[$fileName]; |
|||
} |
|||
|
|||
$lastModificationTime = max(array_merge( |
|||
[$fileName ? filemtime($fileName) : 0], |
|||
array_map(function (ReflectionClass $reflectionTrait): int { |
|||
return $this->getTraitLastModificationTime($reflectionTrait); |
|||
}, $reflectionTrait->getTraits()) |
|||
)); |
|||
|
|||
assert($lastModificationTime !== false); |
|||
|
|||
return $this->loadedFilemtimes[$fileName] = $lastModificationTime; |
|||
} |
|||
} |
|||
@ -0,0 +1,129 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations; |
|||
|
|||
use Doctrine\Common\Lexer\AbstractLexer; |
|||
|
|||
use function ctype_alpha; |
|||
use function is_numeric; |
|||
use function str_replace; |
|||
use function stripos; |
|||
use function strlen; |
|||
use function strpos; |
|||
use function strtolower; |
|||
use function substr; |
|||
|
|||
/** |
|||
* Simple lexer for docblock annotations. |
|||
*/ |
|||
final class DocLexer extends AbstractLexer |
|||
{ |
|||
public const T_NONE = 1; |
|||
public const T_INTEGER = 2; |
|||
public const T_STRING = 3; |
|||
public const T_FLOAT = 4; |
|||
|
|||
// All tokens that are also identifiers should be >= 100 |
|||
public const T_IDENTIFIER = 100; |
|||
public const T_AT = 101; |
|||
public const T_CLOSE_CURLY_BRACES = 102; |
|||
public const T_CLOSE_PARENTHESIS = 103; |
|||
public const T_COMMA = 104; |
|||
public const T_EQUALS = 105; |
|||
public const T_FALSE = 106; |
|||
public const T_NAMESPACE_SEPARATOR = 107; |
|||
public const T_OPEN_CURLY_BRACES = 108; |
|||
public const T_OPEN_PARENTHESIS = 109; |
|||
public const T_TRUE = 110; |
|||
public const T_NULL = 111; |
|||
public const T_COLON = 112; |
|||
public const T_MINUS = 113; |
|||
|
|||
/** @var array<string, int> */ |
|||
protected $noCase = [ |
|||
'@' => self::T_AT, |
|||
',' => self::T_COMMA, |
|||
'(' => self::T_OPEN_PARENTHESIS, |
|||
')' => self::T_CLOSE_PARENTHESIS, |
|||
'{' => self::T_OPEN_CURLY_BRACES, |
|||
'}' => self::T_CLOSE_CURLY_BRACES, |
|||
'=' => self::T_EQUALS, |
|||
':' => self::T_COLON, |
|||
'-' => self::T_MINUS, |
|||
'\\' => self::T_NAMESPACE_SEPARATOR, |
|||
]; |
|||
|
|||
/** @var array<string, int> */ |
|||
protected $withCase = [ |
|||
'true' => self::T_TRUE, |
|||
'false' => self::T_FALSE, |
|||
'null' => self::T_NULL, |
|||
]; |
|||
|
|||
/** |
|||
* Whether the next token starts immediately, or if there were |
|||
* non-captured symbols before that |
|||
*/ |
|||
public function nextTokenIsAdjacent(): bool |
|||
{ |
|||
return $this->token === null |
|||
|| ($this->lookahead !== null |
|||
&& ($this->lookahead['position'] - $this->token['position']) === strlen($this->token['value'])); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
protected function getCatchablePatterns() |
|||
{ |
|||
return [ |
|||
'[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*', |
|||
'(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?', |
|||
'"(?:""|[^"])*+"', |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
protected function getNonCatchablePatterns() |
|||
{ |
|||
return ['\s+', '\*+', '(.)']; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
protected function getType(&$value) |
|||
{ |
|||
$type = self::T_NONE; |
|||
|
|||
if ($value[0] === '"') { |
|||
$value = str_replace('""', '"', substr($value, 1, strlen($value) - 2)); |
|||
|
|||
return self::T_STRING; |
|||
} |
|||
|
|||
if (isset($this->noCase[$value])) { |
|||
return $this->noCase[$value]; |
|||
} |
|||
|
|||
if ($value[0] === '_' || $value[0] === '\\' || ctype_alpha($value[0])) { |
|||
return self::T_IDENTIFIER; |
|||
} |
|||
|
|||
$lowerValue = strtolower($value); |
|||
|
|||
if (isset($this->withCase[$lowerValue])) { |
|||
return $this->withCase[$lowerValue]; |
|||
} |
|||
|
|||
// Checking numeric value |
|||
if (is_numeric($value)) { |
|||
return strpos($value, '.') !== false || stripos($value, 'e') !== false |
|||
? self::T_FLOAT : self::T_INTEGER; |
|||
} |
|||
|
|||
return $type; |
|||
} |
|||
} |
|||
@ -0,0 +1,315 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations; |
|||
|
|||
use InvalidArgumentException; |
|||
use ReflectionClass; |
|||
use ReflectionMethod; |
|||
use ReflectionProperty; |
|||
use RuntimeException; |
|||
|
|||
use function chmod; |
|||
use function file_put_contents; |
|||
use function filemtime; |
|||
use function gettype; |
|||
use function is_dir; |
|||
use function is_file; |
|||
use function is_int; |
|||
use function is_writable; |
|||
use function mkdir; |
|||
use function rename; |
|||
use function rtrim; |
|||
use function serialize; |
|||
use function sha1; |
|||
use function sprintf; |
|||
use function strtr; |
|||
use function tempnam; |
|||
use function uniqid; |
|||
use function unlink; |
|||
use function var_export; |
|||
|
|||
/** |
|||
* File cache reader for annotations. |
|||
* |
|||
* @deprecated the FileCacheReader is deprecated and will be removed |
|||
* in version 2.0.0 of doctrine/annotations. Please use the |
|||
* {@see \Doctrine\Common\Annotations\CachedReader} instead. |
|||
*/ |
|||
class FileCacheReader implements Reader |
|||
{ |
|||
/** @var Reader */ |
|||
private $reader; |
|||
|
|||
/** @var string */ |
|||
private $dir; |
|||
|
|||
/** @var bool */ |
|||
private $debug; |
|||
|
|||
/** @phpstan-var array<string, list<object>> */ |
|||
private $loadedAnnotations = []; |
|||
|
|||
/** @var array<string, string> */ |
|||
private $classNameHashes = []; |
|||
|
|||
/** @var int */ |
|||
private $umask; |
|||
|
|||
/** |
|||
* @param string $cacheDir |
|||
* @param bool $debug |
|||
* @param int $umask |
|||
* |
|||
* @throws InvalidArgumentException |
|||
*/ |
|||
public function __construct(Reader $reader, $cacheDir, $debug = false, $umask = 0002) |
|||
{ |
|||
if (! is_int($umask)) { |
|||
throw new InvalidArgumentException(sprintf( |
|||
'The parameter umask must be an integer, was: %s', |
|||
gettype($umask) |
|||
)); |
|||
} |
|||
|
|||
$this->reader = $reader; |
|||
$this->umask = $umask; |
|||
|
|||
if (! is_dir($cacheDir) && ! @mkdir($cacheDir, 0777 & (~$this->umask), true)) { |
|||
throw new InvalidArgumentException(sprintf( |
|||
'The directory "%s" does not exist and could not be created.', |
|||
$cacheDir |
|||
)); |
|||
} |
|||
|
|||
$this->dir = rtrim($cacheDir, '\\/'); |
|||
$this->debug = $debug; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getClassAnnotations(ReflectionClass $class) |
|||
{ |
|||
if (! isset($this->classNameHashes[$class->name])) { |
|||
$this->classNameHashes[$class->name] = sha1($class->name); |
|||
} |
|||
|
|||
$key = $this->classNameHashes[$class->name]; |
|||
|
|||
if (isset($this->loadedAnnotations[$key])) { |
|||
return $this->loadedAnnotations[$key]; |
|||
} |
|||
|
|||
$path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; |
|||
if (! is_file($path)) { |
|||
$annot = $this->reader->getClassAnnotations($class); |
|||
$this->saveCacheFile($path, $annot); |
|||
|
|||
return $this->loadedAnnotations[$key] = $annot; |
|||
} |
|||
|
|||
$filename = $class->getFilename(); |
|||
if ( |
|||
$this->debug |
|||
&& $filename !== false |
|||
&& filemtime($path) < filemtime($filename) |
|||
) { |
|||
@unlink($path); |
|||
|
|||
$annot = $this->reader->getClassAnnotations($class); |
|||
$this->saveCacheFile($path, $annot); |
|||
|
|||
return $this->loadedAnnotations[$key] = $annot; |
|||
} |
|||
|
|||
return $this->loadedAnnotations[$key] = include $path; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getPropertyAnnotations(ReflectionProperty $property) |
|||
{ |
|||
$class = $property->getDeclaringClass(); |
|||
if (! isset($this->classNameHashes[$class->name])) { |
|||
$this->classNameHashes[$class->name] = sha1($class->name); |
|||
} |
|||
|
|||
$key = $this->classNameHashes[$class->name] . '$' . $property->getName(); |
|||
|
|||
if (isset($this->loadedAnnotations[$key])) { |
|||
return $this->loadedAnnotations[$key]; |
|||
} |
|||
|
|||
$path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; |
|||
if (! is_file($path)) { |
|||
$annot = $this->reader->getPropertyAnnotations($property); |
|||
$this->saveCacheFile($path, $annot); |
|||
|
|||
return $this->loadedAnnotations[$key] = $annot; |
|||
} |
|||
|
|||
$filename = $class->getFilename(); |
|||
if ( |
|||
$this->debug |
|||
&& $filename !== false |
|||
&& filemtime($path) < filemtime($filename) |
|||
) { |
|||
@unlink($path); |
|||
|
|||
$annot = $this->reader->getPropertyAnnotations($property); |
|||
$this->saveCacheFile($path, $annot); |
|||
|
|||
return $this->loadedAnnotations[$key] = $annot; |
|||
} |
|||
|
|||
return $this->loadedAnnotations[$key] = include $path; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getMethodAnnotations(ReflectionMethod $method) |
|||
{ |
|||
$class = $method->getDeclaringClass(); |
|||
if (! isset($this->classNameHashes[$class->name])) { |
|||
$this->classNameHashes[$class->name] = sha1($class->name); |
|||
} |
|||
|
|||
$key = $this->classNameHashes[$class->name] . '#' . $method->getName(); |
|||
|
|||
if (isset($this->loadedAnnotations[$key])) { |
|||
return $this->loadedAnnotations[$key]; |
|||
} |
|||
|
|||
$path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; |
|||
if (! is_file($path)) { |
|||
$annot = $this->reader->getMethodAnnotations($method); |
|||
$this->saveCacheFile($path, $annot); |
|||
|
|||
return $this->loadedAnnotations[$key] = $annot; |
|||
} |
|||
|
|||
$filename = $class->getFilename(); |
|||
if ( |
|||
$this->debug |
|||
&& $filename !== false |
|||
&& filemtime($path) < filemtime($filename) |
|||
) { |
|||
@unlink($path); |
|||
|
|||
$annot = $this->reader->getMethodAnnotations($method); |
|||
$this->saveCacheFile($path, $annot); |
|||
|
|||
return $this->loadedAnnotations[$key] = $annot; |
|||
} |
|||
|
|||
return $this->loadedAnnotations[$key] = include $path; |
|||
} |
|||
|
|||
/** |
|||
* Saves the cache file. |
|||
* |
|||
* @param string $path |
|||
* @param mixed $data |
|||
* |
|||
* @return void |
|||
*/ |
|||
private function saveCacheFile($path, $data) |
|||
{ |
|||
if (! is_writable($this->dir)) { |
|||
throw new InvalidArgumentException(sprintf( |
|||
<<<'EXCEPTION' |
|||
The directory "%s" is not writable. Both the webserver and the console user need access. |
|||
You can manage access rights for multiple users with "chmod +a". |
|||
If your system does not support this, check out the acl package., |
|||
EXCEPTION |
|||
, |
|||
$this->dir |
|||
)); |
|||
} |
|||
|
|||
$tempfile = tempnam($this->dir, uniqid('', true)); |
|||
|
|||
if ($tempfile === false) { |
|||
throw new RuntimeException(sprintf('Unable to create tempfile in directory: %s', $this->dir)); |
|||
} |
|||
|
|||
@chmod($tempfile, 0666 & (~$this->umask)); |
|||
|
|||
$written = file_put_contents( |
|||
$tempfile, |
|||
'<?php return unserialize(' . var_export(serialize($data), true) . ');' |
|||
); |
|||
|
|||
if ($written === false) { |
|||
throw new RuntimeException(sprintf('Unable to write cached file to: %s', $tempfile)); |
|||
} |
|||
|
|||
@chmod($tempfile, 0666 & (~$this->umask)); |
|||
|
|||
if (rename($tempfile, $path) === false) { |
|||
@unlink($tempfile); |
|||
|
|||
throw new RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path)); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getClassAnnotation(ReflectionClass $class, $annotationName) |
|||
{ |
|||
$annotations = $this->getClassAnnotations($class); |
|||
|
|||
foreach ($annotations as $annotation) { |
|||
if ($annotation instanceof $annotationName) { |
|||
return $annotation; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getMethodAnnotation(ReflectionMethod $method, $annotationName) |
|||
{ |
|||
$annotations = $this->getMethodAnnotations($method); |
|||
|
|||
foreach ($annotations as $annotation) { |
|||
if ($annotation instanceof $annotationName) { |
|||
return $annotation; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) |
|||
{ |
|||
$annotations = $this->getPropertyAnnotations($property); |
|||
|
|||
foreach ($annotations as $annotation) { |
|||
if ($annotation instanceof $annotationName) { |
|||
return $annotation; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Clears loaded annotations. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function clearLoadedAnnotations() |
|||
{ |
|||
$this->loadedAnnotations = []; |
|||
} |
|||
} |
|||
@ -0,0 +1,171 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
namespace Doctrine\Common\Annotations; |
|||
|
|||
/** |
|||
* A list of annotations that are implicitly ignored during the parsing process. |
|||
* |
|||
* All names are case sensitive. |
|||
*/ |
|||
final class ImplicitlyIgnoredAnnotationNames |
|||
{ |
|||
private const Reserved = [ |
|||
'Annotation' => true, |
|||
'Attribute' => true, |
|||
'Attributes' => true, |
|||
/* Can we enable this? 'Enum' => true, */ |
|||
'Required' => true, |
|||
'Target' => true, |
|||
]; |
|||
|
|||
private const WidelyUsedNonStandard = [ |
|||
'fix' => true, |
|||
'fixme' => true, |
|||
'override' => true, |
|||
]; |
|||
|
|||
private const PhpDocumentor1 = [ |
|||
'abstract' => true, |
|||
'access' => true, |
|||
'code' => true, |
|||
'deprec' => true, |
|||
'endcode' => true, |
|||
'exception' => true, |
|||
'final' => true, |
|||
'ingroup' => true, |
|||
'inheritdoc' => true, |
|||
'inheritDoc' => true, |
|||
'magic' => true, |
|||
'name' => true, |
|||
'private' => true, |
|||
'static' => true, |
|||
'staticvar' => true, |
|||
'staticVar' => true, |
|||
'toc' => true, |
|||
'tutorial' => true, |
|||
'throw' => true, |
|||
]; |
|||
|
|||
private const PhpDocumentor2 = [ |
|||
'api' => true, |
|||
'author' => true, |
|||
'category' => true, |
|||
'copyright' => true, |
|||
'deprecated' => true, |
|||
'example' => true, |
|||
'filesource' => true, |
|||
'global' => true, |
|||
'ignore' => true, |
|||
/* Can we enable this? 'index' => true, */ |
|||
'internal' => true, |
|||
'license' => true, |
|||
'link' => true, |
|||
'method' => true, |
|||
'package' => true, |
|||
'param' => true, |
|||
'property' => true, |
|||
'property-read' => true, |
|||
'property-write' => true, |
|||
'return' => true, |
|||
'see' => true, |
|||
'since' => true, |
|||
'source' => true, |
|||
'subpackage' => true, |
|||
'throws' => true, |
|||
'todo' => true, |
|||
'TODO' => true, |
|||
'usedby' => true, |
|||
'uses' => true, |
|||
'var' => true, |
|||
'version' => true, |
|||
]; |
|||
|
|||
private const PHPUnit = [ |
|||
'author' => true, |
|||
'after' => true, |
|||
'afterClass' => true, |
|||
'backupGlobals' => true, |
|||
'backupStaticAttributes' => true, |
|||
'before' => true, |
|||
'beforeClass' => true, |
|||
'codeCoverageIgnore' => true, |
|||
'codeCoverageIgnoreStart' => true, |
|||
'codeCoverageIgnoreEnd' => true, |
|||
'covers' => true, |
|||
'coversDefaultClass' => true, |
|||
'coversNothing' => true, |
|||
'dataProvider' => true, |
|||
'depends' => true, |
|||
'doesNotPerformAssertions' => true, |
|||
'expectedException' => true, |
|||
'expectedExceptionCode' => true, |
|||
'expectedExceptionMessage' => true, |
|||
'expectedExceptionMessageRegExp' => true, |
|||
'group' => true, |
|||
'large' => true, |
|||
'medium' => true, |
|||
'preserveGlobalState' => true, |
|||
'requires' => true, |
|||
'runTestsInSeparateProcesses' => true, |
|||
'runInSeparateProcess' => true, |
|||
'small' => true, |
|||
'test' => true, |
|||
'testdox' => true, |
|||
'testWith' => true, |
|||
'ticket' => true, |
|||
'uses' => true, |
|||
]; |
|||
|
|||
private const PhpCheckStyle = ['SuppressWarnings' => true]; |
|||
|
|||
private const PhpStorm = ['noinspection' => true]; |
|||
|
|||
private const PEAR = ['package_version' => true]; |
|||
|
|||
private const PlainUML = [ |
|||
'startuml' => true, |
|||
'enduml' => true, |
|||
]; |
|||
|
|||
private const Symfony = ['experimental' => true]; |
|||
|
|||
private const PhpCodeSniffer = [ |
|||
'codingStandardsIgnoreStart' => true, |
|||
'codingStandardsIgnoreEnd' => true, |
|||
]; |
|||
|
|||
private const SlevomatCodingStandard = ['phpcsSuppress' => true]; |
|||
|
|||
private const PhpStan = [ |
|||
'extends' => true, |
|||
'implements' => true, |
|||
'template' => true, |
|||
'use' => true, |
|||
]; |
|||
|
|||
private const Phan = ['suppress' => true]; |
|||
|
|||
private const Rector = ['noRector' => true]; |
|||
|
|||
public const LIST = self::Reserved |
|||
+ self::WidelyUsedNonStandard |
|||
+ self::PhpDocumentor1 |
|||
+ self::PhpDocumentor2 |
|||
+ self::PHPUnit |
|||
+ self::PhpCheckStyle |
|||
+ self::PhpStorm |
|||
+ self::PEAR |
|||
+ self::PlainUML |
|||
+ self::Symfony |
|||
+ self::SlevomatCodingStandard |
|||
+ self::PhpCodeSniffer |
|||
+ self::PhpStan |
|||
+ self::Phan |
|||
+ self::Rector; |
|||
|
|||
private function __construct() |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations; |
|||
|
|||
use ReflectionClass; |
|||
use ReflectionMethod; |
|||
use ReflectionProperty; |
|||
|
|||
use function call_user_func_array; |
|||
use function get_class; |
|||
|
|||
/** |
|||
* Allows the reader to be used in-place of Doctrine's reader. |
|||
*/ |
|||
class IndexedReader implements Reader |
|||
{ |
|||
/** @var Reader */ |
|||
private $delegate; |
|||
|
|||
public function __construct(Reader $reader) |
|||
{ |
|||
$this->delegate = $reader; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getClassAnnotations(ReflectionClass $class) |
|||
{ |
|||
$annotations = []; |
|||
foreach ($this->delegate->getClassAnnotations($class) as $annot) { |
|||
$annotations[get_class($annot)] = $annot; |
|||
} |
|||
|
|||
return $annotations; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getClassAnnotation(ReflectionClass $class, $annotation) |
|||
{ |
|||
return $this->delegate->getClassAnnotation($class, $annotation); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getMethodAnnotations(ReflectionMethod $method) |
|||
{ |
|||
$annotations = []; |
|||
foreach ($this->delegate->getMethodAnnotations($method) as $annot) { |
|||
$annotations[get_class($annot)] = $annot; |
|||
} |
|||
|
|||
return $annotations; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getMethodAnnotation(ReflectionMethod $method, $annotation) |
|||
{ |
|||
return $this->delegate->getMethodAnnotation($method, $annotation); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getPropertyAnnotations(ReflectionProperty $property) |
|||
{ |
|||
$annotations = []; |
|||
foreach ($this->delegate->getPropertyAnnotations($property) as $annot) { |
|||
$annotations[get_class($annot)] = $annot; |
|||
} |
|||
|
|||
return $annotations; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getPropertyAnnotation(ReflectionProperty $property, $annotation) |
|||
{ |
|||
return $this->delegate->getPropertyAnnotation($property, $annotation); |
|||
} |
|||
|
|||
/** |
|||
* Proxies all methods to the delegate. |
|||
* |
|||
* @param string $method |
|||
* @param mixed[] $args |
|||
* |
|||
* @return mixed |
|||
*/ |
|||
public function __call($method, $args) |
|||
{ |
|||
return call_user_func_array([$this->delegate, $method], $args); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations; |
|||
|
|||
/** |
|||
* Marker interface for PHP7/PHP8 compatible support |
|||
* for named arguments (and constructor property promotion). |
|||
* |
|||
* @deprecated Implementing this interface is deprecated |
|||
* Use the Annotation @NamedArgumentConstructor instead |
|||
*/ |
|||
interface NamedArgumentConstructorAnnotation |
|||
{ |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations; |
|||
|
|||
use ReflectionClass; |
|||
use ReflectionFunction; |
|||
use SplFileObject; |
|||
|
|||
use function is_file; |
|||
use function method_exists; |
|||
use function preg_quote; |
|||
use function preg_replace; |
|||
|
|||
/** |
|||
* Parses a file for namespaces/use/class declarations. |
|||
*/ |
|||
final class PhpParser |
|||
{ |
|||
/** |
|||
* Parses a class. |
|||
* |
|||
* @deprecated use parseUseStatements instead |
|||
* |
|||
* @param ReflectionClass $class A <code>ReflectionClass</code> object. |
|||
* |
|||
* @return array<string, class-string> A list with use statements in the form (Alias => FQN). |
|||
*/ |
|||
public function parseClass(ReflectionClass $class) |
|||
{ |
|||
return $this->parseUseStatements($class); |
|||
} |
|||
|
|||
/** |
|||
* Parse a class or function for use statements. |
|||
* |
|||
* @param ReflectionClass|ReflectionFunction $reflection |
|||
* |
|||
* @psalm-return array<string, string> a list with use statements in the form (Alias => FQN). |
|||
*/ |
|||
public function parseUseStatements($reflection): array |
|||
{ |
|||
if (method_exists($reflection, 'getUseStatements')) { |
|||
return $reflection->getUseStatements(); |
|||
} |
|||
|
|||
$filename = $reflection->getFileName(); |
|||
|
|||
if ($filename === false) { |
|||
return []; |
|||
} |
|||
|
|||
$content = $this->getFileContent($filename, $reflection->getStartLine()); |
|||
|
|||
if ($content === null) { |
|||
return []; |
|||
} |
|||
|
|||
$namespace = preg_quote($reflection->getNamespaceName()); |
|||
$content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content); |
|||
$tokenizer = new TokenParser('<?php ' . $content); |
|||
|
|||
return $tokenizer->parseUseStatements($reflection->getNamespaceName()); |
|||
} |
|||
|
|||
/** |
|||
* Gets the content of the file right up to the given line number. |
|||
* |
|||
* @param string $filename The name of the file to load. |
|||
* @param int $lineNumber The number of lines to read from file. |
|||
* |
|||
* @return string|null The content of the file or null if the file does not exist. |
|||
*/ |
|||
private function getFileContent($filename, $lineNumber) |
|||
{ |
|||
if (! is_file($filename)) { |
|||
return null; |
|||
} |
|||
|
|||
$content = ''; |
|||
$lineCnt = 0; |
|||
$file = new SplFileObject($filename); |
|||
while (! $file->eof()) { |
|||
if ($lineCnt++ === $lineNumber) { |
|||
break; |
|||
} |
|||
|
|||
$content .= $file->fgets(); |
|||
} |
|||
|
|||
return $content; |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations; |
|||
|
|||
use ReflectionClass; |
|||
use ReflectionMethod; |
|||
use ReflectionProperty; |
|||
|
|||
/** |
|||
* Interface for annotation readers. |
|||
*/ |
|||
interface Reader |
|||
{ |
|||
/** |
|||
* Gets the annotations applied to a class. |
|||
* |
|||
* @param ReflectionClass $class The ReflectionClass of the class from which |
|||
* the class annotations should be read. |
|||
* |
|||
* @return array<object> An array of Annotations. |
|||
*/ |
|||
public function getClassAnnotations(ReflectionClass $class); |
|||
|
|||
/** |
|||
* Gets a class annotation. |
|||
* |
|||
* @param ReflectionClass $class The ReflectionClass of the class from which |
|||
* the class annotations should be read. |
|||
* @param class-string<T> $annotationName The name of the annotation. |
|||
* |
|||
* @return T|null The Annotation or NULL, if the requested annotation does not exist. |
|||
* |
|||
* @template T |
|||
*/ |
|||
public function getClassAnnotation(ReflectionClass $class, $annotationName); |
|||
|
|||
/** |
|||
* Gets the annotations applied to a method. |
|||
* |
|||
* @param ReflectionMethod $method The ReflectionMethod of the method from which |
|||
* the annotations should be read. |
|||
* |
|||
* @return array<object> An array of Annotations. |
|||
*/ |
|||
public function getMethodAnnotations(ReflectionMethod $method); |
|||
|
|||
/** |
|||
* Gets a method annotation. |
|||
* |
|||
* @param ReflectionMethod $method The ReflectionMethod to read the annotations from. |
|||
* @param class-string<T> $annotationName The name of the annotation. |
|||
* |
|||
* @return T|null The Annotation or NULL, if the requested annotation does not exist. |
|||
* |
|||
* @template T |
|||
*/ |
|||
public function getMethodAnnotation(ReflectionMethod $method, $annotationName); |
|||
|
|||
/** |
|||
* Gets the annotations applied to a property. |
|||
* |
|||
* @param ReflectionProperty $property The ReflectionProperty of the property |
|||
* from which the annotations should be read. |
|||
* |
|||
* @return array<object> An array of Annotations. |
|||
*/ |
|||
public function getPropertyAnnotations(ReflectionProperty $property); |
|||
|
|||
/** |
|||
* Gets a property annotation. |
|||
* |
|||
* @param ReflectionProperty $property The ReflectionProperty to read the annotations from. |
|||
* @param class-string<T> $annotationName The name of the annotation. |
|||
* |
|||
* @return T|null The Annotation or NULL, if the requested annotation does not exist. |
|||
* |
|||
* @template T |
|||
*/ |
|||
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName); |
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations; |
|||
|
|||
use ReflectionClass; |
|||
use ReflectionMethod; |
|||
use ReflectionProperty; |
|||
|
|||
/** |
|||
* Simple Annotation Reader. |
|||
* |
|||
* This annotation reader is intended to be used in projects where you have |
|||
* full-control over all annotations that are available. |
|||
* |
|||
* @deprecated Deprecated in favour of using AnnotationReader |
|||
*/ |
|||
class SimpleAnnotationReader implements Reader |
|||
{ |
|||
/** @var DocParser */ |
|||
private $parser; |
|||
|
|||
/** |
|||
* Initializes a new SimpleAnnotationReader. |
|||
*/ |
|||
public function __construct() |
|||
{ |
|||
$this->parser = new DocParser(); |
|||
$this->parser->setIgnoreNotImportedAnnotations(true); |
|||
} |
|||
|
|||
/** |
|||
* Adds a namespace in which we will look for annotations. |
|||
* |
|||
* @param string $namespace |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function addNamespace($namespace) |
|||
{ |
|||
$this->parser->addNamespace($namespace); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getClassAnnotations(ReflectionClass $class) |
|||
{ |
|||
return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getMethodAnnotations(ReflectionMethod $method) |
|||
{ |
|||
return $this->parser->parse( |
|||
$method->getDocComment(), |
|||
'method ' . $method->getDeclaringClass()->name . '::' . $method->getName() . '()' |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getPropertyAnnotations(ReflectionProperty $property) |
|||
{ |
|||
return $this->parser->parse( |
|||
$property->getDocComment(), |
|||
'property ' . $property->getDeclaringClass()->name . '::$' . $property->getName() |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getClassAnnotation(ReflectionClass $class, $annotationName) |
|||
{ |
|||
foreach ($this->getClassAnnotations($class) as $annot) { |
|||
if ($annot instanceof $annotationName) { |
|||
return $annot; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getMethodAnnotation(ReflectionMethod $method, $annotationName) |
|||
{ |
|||
foreach ($this->getMethodAnnotations($method) as $annot) { |
|||
if ($annot instanceof $annotationName) { |
|||
return $annot; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) |
|||
{ |
|||
foreach ($this->getPropertyAnnotations($property) as $annot) { |
|||
if ($annot instanceof $annotationName) { |
|||
return $annot; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
@ -0,0 +1,208 @@ |
|||
<?php |
|||
|
|||
namespace Doctrine\Common\Annotations; |
|||
|
|||
use function array_merge; |
|||
use function count; |
|||
use function explode; |
|||
use function strtolower; |
|||
use function token_get_all; |
|||
|
|||
use const PHP_VERSION_ID; |
|||
use const T_AS; |
|||
use const T_COMMENT; |
|||
use const T_DOC_COMMENT; |
|||
use const T_NAME_FULLY_QUALIFIED; |
|||
use const T_NAME_QUALIFIED; |
|||
use const T_NAMESPACE; |
|||
use const T_NS_SEPARATOR; |
|||
use const T_STRING; |
|||
use const T_USE; |
|||
use const T_WHITESPACE; |
|||
|
|||
/** |
|||
* Parses a file for namespaces/use/class declarations. |
|||
*/ |
|||
class TokenParser |
|||
{ |
|||
/** |
|||
* The token list. |
|||
* |
|||
* @phpstan-var list<mixed[]> |
|||
*/ |
|||
private $tokens; |
|||
|
|||
/** |
|||
* The number of tokens. |
|||
* |
|||
* @var int |
|||
*/ |
|||
private $numTokens; |
|||
|
|||
/** |
|||
* The current array pointer. |
|||
* |
|||
* @var int |
|||
*/ |
|||
private $pointer = 0; |
|||
|
|||
/** |
|||
* @param string $contents |
|||
*/ |
|||
public function __construct($contents) |
|||
{ |
|||
$this->tokens = token_get_all($contents); |
|||
|
|||
// The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it |
|||
// saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored |
|||
// doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a |
|||
// docblock. If the first thing in the file is a class without a doc block this would cause calls to |
|||
// getDocBlock() on said class to return our long lost doc_comment. Argh. |
|||
// To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least |
|||
// it's harmless to us. |
|||
token_get_all("<?php\n/**\n *\n */");
|
|||
|
|||
$this->numTokens = count($this->tokens); |
|||
} |
|||
|
|||
/** |
|||
* Gets the next non whitespace and non comment token. |
|||
* |
|||
* @param bool $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped. |
|||
* If FALSE then only whitespace and normal comments are skipped. |
|||
* |
|||
* @return mixed[]|string|null The token if exists, null otherwise. |
|||
*/ |
|||
public function next($docCommentIsComment = true) |
|||
{ |
|||
for ($i = $this->pointer; $i < $this->numTokens; $i++) { |
|||
$this->pointer++; |
|||
if ( |
|||
$this->tokens[$i][0] === T_WHITESPACE || |
|||
$this->tokens[$i][0] === T_COMMENT || |
|||
($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT) |
|||
) { |
|||
continue; |
|||
} |
|||
|
|||
return $this->tokens[$i]; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Parses a single use statement. |
|||
* |
|||
* @return array<string, string> A list with all found class names for a use statement. |
|||
*/ |
|||
public function parseUseStatement() |
|||
{ |
|||
$groupRoot = ''; |
|||
$class = ''; |
|||
$alias = ''; |
|||
$statements = []; |
|||
$explicitAlias = false; |
|||
while (($token = $this->next())) { |
|||
if (! $explicitAlias && $token[0] === T_STRING) { |
|||
$class .= $token[1]; |
|||
$alias = $token[1]; |
|||
} elseif ($explicitAlias && $token[0] === T_STRING) { |
|||
$alias = $token[1]; |
|||
} elseif ( |
|||
PHP_VERSION_ID >= 80000 && |
|||
($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED) |
|||
) { |
|||
$class .= $token[1]; |
|||
|
|||
$classSplit = explode('\\', $token[1]); |
|||
$alias = $classSplit[count($classSplit) - 1]; |
|||
} elseif ($token[0] === T_NS_SEPARATOR) { |
|||
$class .= '\\'; |
|||
$alias = ''; |
|||
} elseif ($token[0] === T_AS) { |
|||
$explicitAlias = true; |
|||
$alias = ''; |
|||
} elseif ($token === ',') { |
|||
$statements[strtolower($alias)] = $groupRoot . $class; |
|||
$class = ''; |
|||
$alias = ''; |
|||
$explicitAlias = false; |
|||
} elseif ($token === ';') { |
|||
$statements[strtolower($alias)] = $groupRoot . $class; |
|||
break; |
|||
} elseif ($token === '{') { |
|||
$groupRoot = $class; |
|||
$class = ''; |
|||
} elseif ($token === '}') { |
|||
continue; |
|||
} else { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return $statements; |
|||
} |
|||
|
|||
/** |
|||
* Gets all use statements. |
|||
* |
|||
* @param string $namespaceName The namespace name of the reflected class. |
|||
* |
|||
* @return array<string, string> A list with all found use statements. |
|||
*/ |
|||
public function parseUseStatements($namespaceName) |
|||
{ |
|||
$statements = []; |
|||
while (($token = $this->next())) { |
|||
if ($token[0] === T_USE) { |
|||
$statements = array_merge($statements, $this->parseUseStatement()); |
|||
continue; |
|||
} |
|||
|
|||
if ($token[0] !== T_NAMESPACE || $this->parseNamespace() !== $namespaceName) { |
|||
continue; |
|||
} |
|||
|
|||
// Get fresh array for new namespace. This is to prevent the parser to collect the use statements |
|||
// for a previous namespace with the same name. This is the case if a namespace is defined twice |
|||
// or if a namespace with the same name is commented out. |
|||
$statements = []; |
|||
} |
|||
|
|||
return $statements; |
|||
} |
|||
|
|||
/** |
|||
* Gets the namespace. |
|||
* |
|||
* @return string The found namespace. |
|||
*/ |
|||
public function parseNamespace() |
|||
{ |
|||
$name = ''; |
|||
while ( |
|||
($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || ( |
|||
PHP_VERSION_ID >= 80000 && |
|||
($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED) |
|||
)) |
|||
) { |
|||
$name .= $token[1]; |
|||
} |
|||
|
|||
return $name; |
|||
} |
|||
|
|||
/** |
|||
* Gets the class name. |
|||
* |
|||
* @return string The found class name. |
|||
*/ |
|||
public function parseClass() |
|||
{ |
|||
// Namespaces and class names are tokenized the same: T_STRINGs |
|||
// separated by T_NS_SEPARATOR so we can use one function to provide |
|||
// both. |
|||
return $this->parseNamespace(); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
Copyright (c) 2006-2018 Doctrine Project |
|||
|
|||
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. |
|||
@ -0,0 +1,9 @@ |
|||
# Doctrine Lexer |
|||
|
|||
Build Status: [](https://travis-ci.org/doctrine/lexer) |
|||
|
|||
Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers. |
|||
|
|||
This lexer is used in Doctrine Annotations and in Doctrine ORM (DQL). |
|||
|
|||
https://www.doctrine-project.org/projects/lexer.html |
|||
@ -0,0 +1,41 @@ |
|||
{ |
|||
"name": "doctrine/lexer", |
|||
"type": "library", |
|||
"description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", |
|||
"keywords": [ |
|||
"php", |
|||
"parser", |
|||
"lexer", |
|||
"annotations", |
|||
"docblock" |
|||
], |
|||
"homepage": "https://www.doctrine-project.org/projects/lexer.html", |
|||
"license": "MIT", |
|||
"authors": [ |
|||
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, |
|||
{"name": "Roman Borschel", "email": "roman@code-factory.org"}, |
|||
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} |
|||
], |
|||
"require": { |
|||
"php": "^7.2 || ^8.0" |
|||
}, |
|||
"require-dev": { |
|||
"doctrine/coding-standard": "^6.0", |
|||
"phpstan/phpstan": "^0.11.8", |
|||
"phpunit/phpunit": "^8.2" |
|||
}, |
|||
"autoload": { |
|||
"psr-4": { "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" } |
|||
}, |
|||
"autoload-dev": { |
|||
"psr-4": { "Doctrine\\Tests\\": "tests/Doctrine" } |
|||
}, |
|||
"extra": { |
|||
"branch-alias": { |
|||
"dev-master": "1.2.x-dev" |
|||
} |
|||
}, |
|||
"config": { |
|||
"sort-packages": true |
|||
} |
|||
} |
|||
@ -0,0 +1,328 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
namespace Doctrine\Common\Lexer; |
|||
|
|||
use ReflectionClass; |
|||
use const PREG_SPLIT_DELIM_CAPTURE; |
|||
use const PREG_SPLIT_NO_EMPTY; |
|||
use const PREG_SPLIT_OFFSET_CAPTURE; |
|||
use function implode; |
|||
use function in_array; |
|||
use function preg_split; |
|||
use function sprintf; |
|||
use function substr; |
|||
|
|||
/** |
|||
* Base class for writing simple lexers, i.e. for creating small DSLs. |
|||
*/ |
|||
abstract class AbstractLexer |
|||
{ |
|||
/** |
|||
* Lexer original input string. |
|||
* |
|||
* @var string |
|||
*/ |
|||
private $input; |
|||
|
|||
/** |
|||
* Array of scanned tokens. |
|||
* |
|||
* Each token is an associative array containing three items: |
|||
* - 'value' : the string value of the token in the input string |
|||
* - 'type' : the type of the token (identifier, numeric, string, input |
|||
* parameter, none) |
|||
* - 'position' : the position of the token in the input string |
|||
* |
|||
* @var array |
|||
*/ |
|||
private $tokens = []; |
|||
|
|||
/** |
|||
* Current lexer position in input string. |
|||
* |
|||
* @var int |
|||
*/ |
|||
private $position = 0; |
|||
|
|||
/** |
|||
* Current peek of current lexer position. |
|||
* |
|||
* @var int |
|||
*/ |
|||
private $peek = 0; |
|||
|
|||
/** |
|||
* The next token in the input. |
|||
* |
|||
* @var array|null |
|||
*/ |
|||
public $lookahead; |
|||
|
|||
/** |
|||
* The last matched/seen token. |
|||
* |
|||
* @var array|null |
|||
*/ |
|||
public $token; |
|||
|
|||
/** |
|||
* Composed regex for input parsing. |
|||
* |
|||
* @var string |
|||
*/ |
|||
private $regex; |
|||
|
|||
/** |
|||
* Sets the input data to be tokenized. |
|||
* |
|||
* The Lexer is immediately reset and the new input tokenized. |
|||
* Any unprocessed tokens from any previous input are lost. |
|||
* |
|||
* @param string $input The input to be tokenized. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function setInput($input) |
|||
{ |
|||
$this->input = $input; |
|||
$this->tokens = []; |
|||
|
|||
$this->reset(); |
|||
$this->scan($input); |
|||
} |
|||
|
|||
/** |
|||
* Resets the lexer. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function reset() |
|||
{ |
|||
$this->lookahead = null; |
|||
$this->token = null; |
|||
$this->peek = 0; |
|||
$this->position = 0; |
|||
} |
|||
|
|||
/** |
|||
* Resets the peek pointer to 0. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function resetPeek() |
|||
{ |
|||
$this->peek = 0; |
|||
} |
|||
|
|||
/** |
|||
* Resets the lexer position on the input to the given position. |
|||
* |
|||
* @param int $position Position to place the lexical scanner. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function resetPosition($position = 0) |
|||
{ |
|||
$this->position = $position; |
|||
} |
|||
|
|||
/** |
|||
* Retrieve the original lexer's input until a given position. |
|||
* |
|||
* @param int $position |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function getInputUntilPosition($position) |
|||
{ |
|||
return substr($this->input, 0, $position); |
|||
} |
|||
|
|||
/** |
|||
* Checks whether a given token matches the current lookahead. |
|||
* |
|||
* @param int|string $token |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function isNextToken($token) |
|||
{ |
|||
return $this->lookahead !== null && $this->lookahead['type'] === $token; |
|||
} |
|||
|
|||
/** |
|||
* Checks whether any of the given tokens matches the current lookahead. |
|||
* |
|||
* @param array $tokens |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function isNextTokenAny(array $tokens) |
|||
{ |
|||
return $this->lookahead !== null && in_array($this->lookahead['type'], $tokens, true); |
|||
} |
|||
|
|||
/** |
|||
* Moves to the next token in the input string. |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function moveNext() |
|||
{ |
|||
$this->peek = 0; |
|||
$this->token = $this->lookahead; |
|||
$this->lookahead = isset($this->tokens[$this->position]) |
|||
? $this->tokens[$this->position++] : null; |
|||
|
|||
return $this->lookahead !== null; |
|||
} |
|||
|
|||
/** |
|||
* Tells the lexer to skip input tokens until it sees a token with the given value. |
|||
* |
|||
* @param string $type The token type to skip until. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function skipUntil($type) |
|||
{ |
|||
while ($this->lookahead !== null && $this->lookahead['type'] !== $type) { |
|||
$this->moveNext(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Checks if given value is identical to the given token. |
|||
* |
|||
* @param mixed $value |
|||
* @param int|string $token |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function isA($value, $token) |
|||
{ |
|||
return $this->getType($value) === $token; |
|||
} |
|||
|
|||
/** |
|||
* Moves the lookahead token forward. |
|||
* |
|||
* @return array|null The next token or NULL if there are no more tokens ahead. |
|||
*/ |
|||
public function peek() |
|||
{ |
|||
if (isset($this->tokens[$this->position + $this->peek])) { |
|||
return $this->tokens[$this->position + $this->peek++]; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Peeks at the next token, returns it and immediately resets the peek. |
|||
* |
|||
* @return array|null The next token or NULL if there are no more tokens ahead. |
|||
*/ |
|||
public function glimpse() |
|||
{ |
|||
$peek = $this->peek(); |
|||
$this->peek = 0; |
|||
|
|||
return $peek; |
|||
} |
|||
|
|||
/** |
|||
* Scans the input string for tokens. |
|||
* |
|||
* @param string $input A query string. |
|||
* |
|||
* @return void |
|||
*/ |
|||
protected function scan($input) |
|||
{ |
|||
if (! isset($this->regex)) { |
|||
$this->regex = sprintf( |
|||
'/(%s)|%s/%s', |
|||
implode(')|(', $this->getCatchablePatterns()), |
|||
implode('|', $this->getNonCatchablePatterns()), |
|||
$this->getModifiers() |
|||
); |
|||
} |
|||
|
|||
$flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; |
|||
$matches = preg_split($this->regex, $input, -1, $flags); |
|||
|
|||
if ($matches === false) { |
|||
// Work around https://bugs.php.net/78122 |
|||
$matches = [[$input, 0]]; |
|||
} |
|||
|
|||
foreach ($matches as $match) { |
|||
// Must remain before 'value' assignment since it can change content |
|||
$type = $this->getType($match[0]); |
|||
|
|||
$this->tokens[] = [ |
|||
'value' => $match[0], |
|||
'type' => $type, |
|||
'position' => $match[1], |
|||
]; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Gets the literal for a given token. |
|||
* |
|||
* @param int|string $token |
|||
* |
|||
* @return int|string |
|||
*/ |
|||
public function getLiteral($token) |
|||
{ |
|||
$className = static::class; |
|||
$reflClass = new ReflectionClass($className); |
|||
$constants = $reflClass->getConstants(); |
|||
|
|||
foreach ($constants as $name => $value) { |
|||
if ($value === $token) { |
|||
return $className . '::' . $name; |
|||
} |
|||
} |
|||
|
|||
return $token; |
|||
} |
|||
|
|||
/** |
|||
* Regex modifiers |
|||
* |
|||
* @return string |
|||
*/ |
|||
protected function getModifiers() |
|||
{ |
|||
return 'iu'; |
|||
} |
|||
|
|||
/** |
|||
* Lexical catchable patterns. |
|||
* |
|||
* @return array |
|||
*/ |
|||
abstract protected function getCatchablePatterns(); |
|||
|
|||
/** |
|||
* Lexical non-catchable patterns. |
|||
* |
|||
* @return array |
|||
*/ |
|||
abstract protected function getNonCatchablePatterns(); |
|||
|
|||
/** |
|||
* Retrieve token type. Also processes the token value if necessary. |
|||
* |
|||
* @param string $value |
|||
* |
|||
* @return int|string|null |
|||
*/ |
|||
abstract protected function getType(&$value); |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
/vendor |
|||
/.idea |
|||
composer.phar |
|||
composer.lock |
|||
.DS_Store |
|||
@ -0,0 +1,26 @@ |
|||
Apache Licence是著名的非盈利开源组织Apache采用的协议。 |
|||
该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, |
|||
允许代码修改,再作为开源或商业软件发布。需要满足 |
|||
的条件: |
|||
1. 需要给代码的用户一份Apache Licence ; |
|||
2. 如果你修改了代码,需要在被修改的文件中说明; |
|||
3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 |
|||
带有原来代码中的协议,商标,专利声明和其他原来作者规 |
|||
定需要包含的说明; |
|||
4. 如果再发布的产品中包含一个Notice文件,则在Notice文 |
|||
件中需要带有本协议内容。你可以在Notice中增加自己的 |
|||
许可,但不可以表现为对Apache Licence构成更改。 |
|||
具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
|||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
|||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
|||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||
POSSIBILITY OF SUCH DAMAGE. |
|||
@ -0,0 +1,114 @@ |
|||
# edward-captcha |
|||
|
|||
thinkphp6 验证码类库,支持base64 |
|||
|
|||
## 安装 |
|||
> composer require edward1108/edward-captcha |
|||
|
|||
## 前后端分离中使用 |
|||
###### 生成短信验证码 |
|||
``` |
|||
|
|||
use edward\captcha\facade\CaptchaApi; |
|||
|
|||
$data = CaptchaApi::createSMS($phone); |
|||
|
|||
其中$data 返回为: |
|||
{ |
|||
"key": "13800138000", |
|||
"code": "20" |
|||
} |
|||
|
|||
phone: 为手机号 |
|||
key: 返回给前端,用于前端提交验证 |
|||
code: 验证码值,用短信发送出去 |
|||
``` |
|||
|
|||
###### 验证短信验证码 |
|||
``` |
|||
CaptchaApi::checkSMS($code,$key); |
|||
|
|||
其中code为用户输入的短信验证码, key为上面返回到key 用于替代session, 同时使用cache作为缓冲。 |
|||
``` |
|||
|
|||
###### 生成图片验证码 |
|||
``` |
|||
|
|||
use edward\captcha\facade\CaptchaApi; |
|||
|
|||
$data = CaptchaApi::create(); |
|||
|
|||
其中$data 返回为: |
|||
{ |
|||
"base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPoAAAA+CAMAAAA1S/atAAAAUVBMVEXz+/5PIIu7wNCXyJjEr5mY\r\noKKqrMbcyc+wnp3W0brRm5zLysbKxOGMcrZjO5lfNpC1qNLFurRwTJZ4Vqfe3++hjcSSeKKBYpys\r\nn7duSpl/TpGMCFihAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAF4UlEQVRogeWbi3rjKAxGoblNEif1\r\nbuzO7L7/gy6SwAgQGOw4ab/9O800tsE6kcTNRKkGHQ7we6i48hcqPX48Hltu2Kr9Hn73aICaXpmu\r\n12UVE3QVunBX0qboCK3+3+gfHx9w/zTwcugXUKHmH4P+oRSw/6r2OmLPstflejv66VRVbVk+1w27\r\nZISMXoS2Au5K9Ixy6KcCekOkAHcGHamXo4PWoB9RwonTk9BBCG1fmd6MnpPhLqIbDtNyxTAZ4YXC\r\nxVeQXGQ+17cSYJe9jrFSyU6fU5sFcy38VkLqIrpxez7qWW/+Hq1oAU9WufOG2ngxy74J+vls/p2r\r\nLm3p90SVA76Ebru05beWhNxV7E2jHVHfDt2/lgXQp9N26LlOkbTfPz3Tz+fudtf689bPXbkQvZzj\r\nToQtodshywbof/2trR7DzKWLcp2oa9j9ayA3WltGfoGuHPo0R6n9OSC/jV+/zX/3qNgwmnC4P3w4\r\nHE6S14VKmSrG7f3j4f58+syHwME4Hds5GrgBwn4wmGNQanSXfvpwSNDFSgPNBHx/g4Czb0Sny3Ok\r\nSiG6voRmame6Beu8BWQTmPSF4fDJOEJ0raRKSXy0lWHvbjbX6K3o89XoYKDA3rEwj7yGUWBafgiH\r\nLxkBqsuy24FmKdc74nbocrSvRQfsS2KmNuk8TqkcovdoEHR6HXN7gKBFdlvJNBUpBDwEWj+FWybP\r\nV6KTyy37ZHnIOoQBb3zR2f6e/kQxBout5EphfnE4zPQHQw/JZtFzLdwTvE5mBgrYDSDv2u94DtFH\r\nPBNNQDRjZ+qIHTyO2BV9IaEfj5mJ/jp0slQJLbA7NPQG9RaegUYA0Xts+0N05nI6QNPk6x/MXSKv\r\nnFTG7asXUgfoI8+suDPOKXG5PazR36CgaxtYzT19KjxntYpdztAVsJtwz0+ozcV+SWFzdJkcjxP6\r\nPRjJAjquge0n9KCMjuMIaSw63qzkdeR27Hl0lTzk6G9MY6ZUvXQ/jiPgcz5Cp+lSgj6RuwOm9wzR\r\niV3lct2GCLEX0DcXEgyPANB6XQF7jJ6QC+jmZNrC4/Ic5IyMrkU9GTYSsfuBnSqip+SI/s+8zSe3\r\nQBej2wIyO4k+2ucLzWTddwldIMepUYzep+zTyI6h47WPCvZnR0A3jp1jv/GOPYvuyXegCf3y7x0F\r\nDSaqSxvWGN3jqkr2QNUtvKiJSkdeByMY+tSYcnLz1rL7Zd6gzYrZ+Xh+Il3Ovg7dJ7gOcx1Gc+4d\r\nC4c42svoMfuU6wF2F7CLErjXd24PtzoD5LwxY7z+U+Dku52PeLsSkqBH7NTCW07m8gK0FY0DIvoa\r\nvl3+FExJ9W0cYeJ85ytU/cTgJ7YhuVLM6+6ZTtxJB/Yh9sTN2Of79evUwrfRF9CJne4fVuRWbfzy\r\nDVLv4WevXKp7dHpJxiesUo7r36RFzigJn9VUiV9Ch8EhLMHBimxQC6zS3G04cHJ3FUb7Lq46HZrp\r\nydqUPVNEyavjQb9eSV9GT80kB/ZTSzrQOcdeksChA27PXihS90xEWCBJVI3u5iOUt8GKrCcvL4h3\r\nXbKmHXLHSyQSeuWTMDVPX48uzenp+MSOU5ok0OcqTcK8pHpyqr1A32InFE+eQ+toJGPYW4xTLdyt\r\n5PYGGfxGF+lo84HWnl1h29aKruq5Vb6Fn7uFhN+2ugVFL/wtY0d/L0A35f2IfzPpFL91Zc+X1B7c\r\n9mom13etuQ6CEpuzJ0Pe9kVNXzRgB+3gkWe7A4PB37Zah36R2K0Wuu9FXrdaio5bjWL21XpBroda\r\n5vWpaEOXNKPXen2F7Hw0O0haUKF//dbiKzBPrPEHoG9h4Q8J+E1MfHkzt0jvsfFNW1gj8VxfshM3\r\nr8xubsUXud4rtsae23+9ULnaiPo7sHvl/bS0Prmu7+B1tzXd6tno2apen+vJFweiLZvfzuurd5g4\r\npV8XibZsPhl9da6v3EMY63Xo2dpq0eUvky7S9RpHfHaj7raqC3jpO30LFTzkJ2W3Z2+smmaOvspZ\r\nX+d/MZUz1l7P1Q0AAAAASUVORK5CYII=\r\n", |
|||
"key": "$2y$10$JS3S//sVv5YhKZmxFdh4WevMPF3mhV0YpNr76daQs3acpv/JOUUIW", |
|||
"md5": "c16a5320fa475530d9583c34fd356ef5", |
|||
"code": "20" |
|||
} |
|||
|
|||
base64: 图片base64地址 |
|||
key: 用于前端提交验证 |
|||
md5: 验证码端md5值,用于前端自我验证先,验证通过再请求发送 验证码和key到后台进行验证 |
|||
code: 验证码值,方便用于postman测试和自动化测试,切记正式上线,记得返回api是进行unset() 掉该值,避免被别人利用 |
|||
``` |
|||
|
|||
###### 验证图片验证码 |
|||
``` |
|||
CaptchaApi::check($code,$key); |
|||
|
|||
其中code为用户输入的验证码, key为上面返回到key 用于替代session, 同时使用cache作为缓冲 |
|||
``` |
|||
|
|||
## 来源 |
|||
~~~ |
|||
基于think-captcha进行扩展,保留了think-captcha所有功能,用于非前后端分离项目 |
|||
~~~ |
|||
|
|||
## 使用 |
|||
|
|||
### 在控制器中输出验证码 |
|||
|
|||
在控制器的操作方法中使用 |
|||
|
|||
~~~ |
|||
public function captcha($id = '') |
|||
{ |
|||
return captcha($id); |
|||
} |
|||
~~~ |
|||
然后注册对应的路由来输出验证码 |
|||
|
|||
|
|||
### 模板里输出验证码 |
|||
|
|||
首先要在你应用的路由定义文件中,注册一个验证码路由规则。 |
|||
|
|||
~~~ |
|||
\think\facade\Route::get('captcha/[:id]', "\\edward\\captcha\\CaptchaController@index"); |
|||
~~~ |
|||
|
|||
然后就可以在模板文件中使用 |
|||
~~~ |
|||
<div>{:captcha_img()}</div> |
|||
~~~ |
|||
或者 |
|||
~~~ |
|||
<div><img src="{:captcha_src()}" alt="captcha" /></div> |
|||
~~~ |
|||
> 上面两种的最终效果是一样的 |
|||
|
|||
|
|||
### 控制器里验证 |
|||
|
|||
使用TP的内置验证功能即可 |
|||
~~~ |
|||
$this->validate($data,[ |
|||
'captcha|验证码'=>'require|captcha' |
|||
]); |
|||
~~~ |
|||
或者手动验证 |
|||
~~~ |
|||
if(!captcha_check($captcha)){ |
|||
//验证失败 |
|||
}; |
|||
~~~ |
|||
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 30 KiB |
@ -0,0 +1,34 @@ |
|||
{ |
|||
"name": "edward1108/edward-captcha", |
|||
"description": "ThinkPHP6验证码(图片、短信)支持api友好化", |
|||
"authors": [ |
|||
{ |
|||
"name": "edward", |
|||
"email": "xuyuanhua1987@163.com" |
|||
} |
|||
], |
|||
"require": { |
|||
"php": ">=7.1.0", |
|||
"topthink/framework": "^6.0.0" |
|||
}, |
|||
"homepage": "https://github.com/Edward1108/edward-captcha", |
|||
"license": "MIT", |
|||
"autoload": { |
|||
"psr-4": { |
|||
"edward\\captcha\\": "src/" |
|||
}, |
|||
"files": [ |
|||
"src/helper.php" |
|||
] |
|||
}, |
|||
"extra": { |
|||
"think": { |
|||
"services": [ |
|||
"edward\\captcha\\CaptchaService" |
|||
], |
|||
"config": { |
|||
"captcha": "src/config.php" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,340 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace edward\captcha; |
|||
|
|||
use Exception; |
|||
use think\Config; |
|||
use think\Response; |
|||
use think\Session; |
|||
|
|||
class Captcha |
|||
{ |
|||
private $im = null; // 验证码图片实例 |
|||
private $color = null; // 验证码字体颜色 |
|||
|
|||
/** |
|||
* @var Config|null |
|||
*/ |
|||
private $config = null; |
|||
|
|||
/** |
|||
* @var Session|null |
|||
*/ |
|||
private $session = null; |
|||
|
|||
// 验证码字符集合 |
|||
protected $codeSet = '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY'; |
|||
// 验证码过期时间(s) |
|||
protected $expire = 1800; |
|||
// 使用中文验证码 |
|||
protected $useZh = false; |
|||
// 中文验证码字符串 |
|||
protected $zhSet = '们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书术状厂须离再目海交权且儿青才证低越际八试规斯近注办布门铁需走议县兵固除般引齿千胜细影济白格效置推空配刀叶率述今选养德话查差半敌始片施响收华觉备名红续均药标记难存测士身紧液派准斤角降维板许破述技消底床田势端感往神便贺村构照容非搞亚磨族火段算适讲按值美态黄易彪服早班麦削信排台声该击素张密害侯草何树肥继右属市严径螺检左页抗苏显苦英快称坏移约巴材省黑武培著河帝仅针怎植京助升王眼她抓含苗副杂普谈围食射源例致酸旧却充足短划剂宣环落首尺波承粉践府鱼随考刻靠够满夫失包住促枝局菌杆周护岩师举曲春元超负砂封换太模贫减阳扬江析亩木言球朝医校古呢稻宋听唯输滑站另卫字鼓刚写刘微略范供阿块某功套友限项余倒卷创律雨让骨远帮初皮播优占死毒圈伟季训控激找叫云互跟裂粮粒母练塞钢顶策双留误础吸阻故寸盾晚丝女散焊功株亲院冷彻弹错散商视艺灭版烈零室轻血倍缺厘泵察绝富城冲喷壤简否柱李望盘磁雄似困巩益洲脱投送奴侧润盖挥距触星松送获兴独官混纪依未突架宽冬章湿偏纹吃执阀矿寨责熟稳夺硬价努翻奇甲预职评读背协损棉侵灰虽矛厚罗泥辟告卵箱掌氧恩爱停曾溶营终纲孟钱待尽俄缩沙退陈讨奋械载胞幼哪剥迫旋征槽倒握担仍呀鲜吧卡粗介钻逐弱脚怕盐末阴丰雾冠丙街莱贝辐肠付吉渗瑞惊顿挤秒悬姆烂森糖圣凹陶词迟蚕亿矩康遵牧遭幅园腔订香肉弟屋敏恢忘编印蜂急拿扩伤飞露核缘游振操央伍域甚迅辉异序免纸夜乡久隶缸夹念兰映沟乙吗儒杀汽磷艰晶插埃燃欢铁补咱芽永瓦倾阵碳演威附牙芽永瓦斜灌欧献顺猪洋腐请透司危括脉宜笑若尾束壮暴企菜穗楚汉愈绿拖牛份染既秋遍锻玉夏疗尖殖井费州访吹荣铜沿替滚客召旱悟刺脑措贯藏敢令隙炉壳硫煤迎铸粘探临薄旬善福纵择礼愿伏残雷延烟句纯渐耕跑泽慢栽鲁赤繁境潮横掉锥希池败船假亮谓托伙哲怀割摆贡呈劲财仪沉炼麻罪祖息车穿货销齐鼠抽画饲龙库守筑房歌寒喜哥洗蚀废纳腹乎录镜妇恶脂庄擦险赞钟摇典柄辩竹谷卖乱虚桥奥伯赶垂途额壁网截野遗静谋弄挂课镇妄盛耐援扎虑键归符庆聚绕摩忙舞遇索顾胶羊湖钉仁音迹碎伸灯避泛亡答勇频皇柳哈揭甘诺概宪浓岛袭谁洪谢炮浇斑讯懂灵蛋闭孩释乳巨徒私银伊景坦累匀霉杜乐勒隔弯绩招绍胡呼痛峰零柴簧午跳居尚丁秦稍追梁折耗碱殊岗挖氏刃剧堆赫荷胸衡勤膜篇登驻案刊秧缓凸役剪川雪链渔啦脸户洛孢勃盟买杨宗焦赛旗滤硅炭股坐蒸凝竟陷枪黎救冒暗洞犯筒您宋弧爆谬涂味津臂障褐陆啊健尊豆拔莫抵桑坡缝警挑污冰柬嘴啥饭塑寄赵喊垫丹渡耳刨虎笔稀昆浪萨茶滴浅拥穴覆伦娘吨浸袖珠雌妈紫戏塔锤震岁貌洁剖牢锋疑霸闪埔猛诉刷狠忽灾闹乔唐漏闻沈熔氯荒茎男凡抢像浆旁玻亦忠唱蒙予纷捕锁尤乘乌智淡允叛畜俘摸锈扫毕璃宝芯爷鉴秘净蒋钙肩腾枯抛轨堂拌爸循诱祝励肯酒绳穷塘燥泡袋朗喂铝软渠颗惯贸粪综墙趋彼届墨碍启逆卸航衣孙龄岭骗休借'; |
|||
// 使用背景图片 |
|||
protected $useImgBg = false; |
|||
// 验证码字体大小(px) |
|||
protected $fontSize = 25; |
|||
// 是否画混淆曲线 |
|||
protected $useCurve = true; |
|||
// 是否添加杂点 |
|||
protected $useNoise = true; |
|||
// 验证码图片高度 |
|||
protected $imageH = 0; |
|||
// 验证码图片宽度 |
|||
protected $imageW = 0; |
|||
// 验证码位数 |
|||
protected $length = 5; |
|||
// 验证码字体,不设置随机获取 |
|||
protected $fontttf = ''; |
|||
// 背景颜色 |
|||
protected $bg = [243, 251, 254]; |
|||
//算术验证码 |
|||
protected $math = false; |
|||
|
|||
/** |
|||
* 架构方法 设置参数 |
|||
* @access public |
|||
* @param Config $config |
|||
* @param Session $session |
|||
*/ |
|||
public function __construct(Config $config, Session $session) |
|||
{ |
|||
$this->config = $config; |
|||
$this->session = $session; |
|||
} |
|||
|
|||
/** |
|||
* 配置验证码 |
|||
* @param string|null $config |
|||
*/ |
|||
protected function configure(string $config = null): void |
|||
{ |
|||
if (is_null($config)) { |
|||
$config = $this->config->get('captcha', []); |
|||
} else { |
|||
$config = $this->config->get('captcha.' . $config, []); |
|||
} |
|||
|
|||
foreach ($config as $key => $val) { |
|||
if (property_exists($this, $key)) { |
|||
$this->{$key} = $val; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 创建验证码 |
|||
* @return array |
|||
* @throws Exception |
|||
*/ |
|||
protected function generate(): array |
|||
{ |
|||
$bag = ''; |
|||
|
|||
if ($this->math) { |
|||
$this->useZh = false; |
|||
$this->length = 5; |
|||
|
|||
$x = random_int(10, 30); |
|||
$y = random_int(1, 9); |
|||
$bag = "{$x} + {$y} = "; |
|||
$key = $x + $y; |
|||
$key .= ''; |
|||
} else { |
|||
if ($this->useZh) { |
|||
$characters = preg_split('/(?<!^)(?!$)/u', $this->zhSet); |
|||
} else { |
|||
$characters = str_split($this->codeSet); |
|||
} |
|||
|
|||
for ($i = 0; $i < $this->length; $i++) { |
|||
$bag .= $characters[rand(0, count($characters) - 1)]; |
|||
} |
|||
|
|||
$key = mb_strtolower($bag, 'UTF-8'); |
|||
} |
|||
|
|||
$hash = password_hash($key, PASSWORD_BCRYPT, ['cost' => 10]); |
|||
|
|||
$this->session->set('captcha', [ |
|||
'key' => $hash, |
|||
]); |
|||
|
|||
return [ |
|||
'value' => $bag, |
|||
'key' => $hash, |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* 验证验证码是否正确 |
|||
* @access public |
|||
* @param string $code 用户验证码 |
|||
* @return bool 用户验证码是否正确 |
|||
*/ |
|||
public function check(string $code): bool |
|||
{ |
|||
if (!$this->session->has('captcha')) { |
|||
return false; |
|||
} |
|||
|
|||
$key = $this->session->get('captcha.key'); |
|||
|
|||
$code = mb_strtolower($code, 'UTF-8'); |
|||
|
|||
$res = password_verify($code, $key); |
|||
|
|||
if ($res) { |
|||
$this->session->delete('captcha'); |
|||
} |
|||
|
|||
return $res; |
|||
} |
|||
|
|||
/** |
|||
* 输出验证码并把验证码的值保存的session中 |
|||
* @access public |
|||
* @param null|string $config |
|||
* @param bool $api |
|||
* @return Response |
|||
*/ |
|||
public function create(string $config = null, bool $api = false): Response |
|||
{ |
|||
$this->configure($config); |
|||
|
|||
$generator = $this->generate(); |
|||
|
|||
// 图片宽(px) |
|||
$this->imageW || $this->imageW = $this->length * $this->fontSize * 1.5 + $this->length * $this->fontSize / 2; |
|||
// 图片高(px) |
|||
$this->imageH || $this->imageH = $this->fontSize * 2.5; |
|||
// 建立一幅 $this->imageW x $this->imageH 的图像 |
|||
$this->im = imagecreate($this->imageW, $this->imageH); |
|||
// 设置背景 |
|||
imagecolorallocate($this->im, $this->bg[0], $this->bg[1], $this->bg[2]); |
|||
|
|||
// 验证码字体随机颜色 |
|||
$this->color = imagecolorallocate($this->im, mt_rand(1, 150), mt_rand(1, 150), mt_rand(1, 150)); |
|||
|
|||
// 验证码使用随机字体 |
|||
$ttfPath = __DIR__ . '/../assets/' . ($this->useZh ? 'zhttfs' : 'ttfs') . '/'; |
|||
|
|||
if (empty($this->fontttf)) { |
|||
$dir = dir($ttfPath); |
|||
$ttfs = []; |
|||
while (false !== ($file = $dir->read())) { |
|||
if ('.' != $file[0] && substr($file, -4) == '.ttf') { |
|||
$ttfs[] = $file; |
|||
} |
|||
} |
|||
$dir->close(); |
|||
$this->fontttf = $ttfs[array_rand($ttfs)]; |
|||
} |
|||
|
|||
$fontttf = $ttfPath . $this->fontttf; |
|||
|
|||
if ($this->useImgBg) { |
|||
$this->background(); |
|||
} |
|||
|
|||
if ($this->useNoise) { |
|||
// 绘杂点 |
|||
$this->writeNoise(); |
|||
} |
|||
if ($this->useCurve) { |
|||
// 绘干扰线 |
|||
$this->writeCurve(); |
|||
} |
|||
|
|||
// 绘验证码 |
|||
$text = $this->useZh ? preg_split('/(?<!^)(?!$)/u', $generator['value']) : str_split($generator['value']); // 验证码 |
|||
|
|||
foreach ($text as $index => $char) { |
|||
|
|||
$x = $this->fontSize * ($index + 1) * mt_rand(1.2, 1.6) * ($this->math ? 1 : 1.5); |
|||
$y = $this->fontSize + mt_rand(10, 20); |
|||
$angle = $this->math ? 0 : mt_rand(-40, 40); |
|||
|
|||
imagettftext($this->im, $this->fontSize, $angle, $x, $y, $this->color, $fontttf, $char); |
|||
} |
|||
|
|||
ob_start(); |
|||
// 输出图像 |
|||
imagepng($this->im); |
|||
$content = ob_get_clean(); |
|||
imagedestroy($this->im); |
|||
|
|||
return response($content, 200, ['Content-Length' => strlen($content)])->contentType('image/png'); |
|||
} |
|||
|
|||
/** |
|||
* 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数) |
|||
* |
|||
* 高中的数学公式咋都忘了涅,写出来 |
|||
* 正弦型函数解析式:y=Asin(ωx+φ)+b |
|||
* 各常数值对函数图像的影响: |
|||
* A:决定峰值(即纵向拉伸压缩的倍数) |
|||
* b:表示波形在Y轴的位置关系或纵向移动距离(上加下减) |
|||
* φ:决定波形与X轴位置关系或横向移动距离(左加右减) |
|||
* ω:决定周期(最小正周期T=2π/∣ω∣) |
|||
* |
|||
*/ |
|||
protected function writeCurve(): void |
|||
{ |
|||
$px = $py = 0; |
|||
|
|||
// 曲线前部分 |
|||
$A = mt_rand(1, $this->imageH / 2); // 振幅 |
|||
$b = mt_rand(-$this->imageH / 4, $this->imageH / 4); // Y轴方向偏移量 |
|||
$f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量 |
|||
$T = mt_rand($this->imageH, $this->imageW * 2); // 周期 |
|||
$w = (2 * M_PI) / $T; |
|||
|
|||
$px1 = 0; // 曲线横坐标起始位置 |
|||
$px2 = mt_rand($this->imageW / 2, $this->imageW * 0.8); // 曲线横坐标结束位置 |
|||
|
|||
for ($px = $px1; $px <= $px2; $px = $px + 1) { |
|||
if (0 != $w) { |
|||
$py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b |
|||
$i = (int)($this->fontSize / 5); |
|||
while ($i > 0) { |
|||
imagesetpixel($this->im, $px + $i, $py + $i, $this->color); // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出(不用这while循环)性能要好很多 |
|||
$i--; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 曲线后部分 |
|||
$A = mt_rand(1, $this->imageH / 2); // 振幅 |
|||
$f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量 |
|||
$T = mt_rand($this->imageH, $this->imageW * 2); // 周期 |
|||
$w = (2 * M_PI) / $T; |
|||
$b = $py - $A * sin($w * $px + $f) - $this->imageH / 2; |
|||
$px1 = $px2; |
|||
$px2 = $this->imageW; |
|||
|
|||
for ($px = $px1; $px <= $px2; $px = $px + 1) { |
|||
if (0 != $w) { |
|||
$py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b |
|||
$i = (int)($this->fontSize / 5); |
|||
while ($i > 0) { |
|||
imagesetpixel($this->im, $px + $i, $py + $i, $this->color); |
|||
$i--; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 画杂点 |
|||
* 往图片上写不同颜色的字母或数字 |
|||
*/ |
|||
protected function writeNoise(): void |
|||
{ |
|||
$codeSet = '2345678abcdefhijkmnpqrstuvwxyz'; |
|||
for ($i = 0; $i < 10; $i++) { |
|||
//杂点颜色 |
|||
$noiseColor = imagecolorallocate($this->im, mt_rand(150, 225), mt_rand(150, 225), mt_rand(150, 225)); |
|||
for ($j = 0; $j < 5; $j++) { |
|||
// 绘杂点 |
|||
imagestring($this->im, 5, mt_rand(-10, $this->imageW), mt_rand(-10, $this->imageH), $codeSet[mt_rand(0, 29)], $noiseColor); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 绘制背景图片 |
|||
* 注:如果验证码输出图片比较大,将占用比较多的系统资源 |
|||
*/ |
|||
protected function background(): void |
|||
{ |
|||
$path = __DIR__ . '/../assets/bgs/'; |
|||
$dir = dir($path); |
|||
|
|||
$bgs = []; |
|||
while (false !== ($file = $dir->read())) { |
|||
if ('.' != $file[0] && substr($file, -4) == '.jpg') { |
|||
$bgs[] = $path . $file; |
|||
} |
|||
} |
|||
$dir->close(); |
|||
|
|||
$gb = $bgs[array_rand($bgs)]; |
|||
|
|||
list($width, $height) = @getimagesize($gb); |
|||
// Resample |
|||
$bgImage = @imagecreatefromjpeg($gb); |
|||
@imagecopyresampled($this->im, $bgImage, 0, 0, 0, 0, $this->imageW, $this->imageH, $width, $height); |
|||
@imagedestroy($bgImage); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,369 @@ |
|||
<?php |
|||
|
|||
namespace edward\captcha; |
|||
|
|||
use Exception; |
|||
use think\Config; |
|||
use think\Response; |
|||
use think\Cache; |
|||
|
|||
class CaptchaApi |
|||
{ |
|||
private $im = null; // 验证码图片实例 |
|||
private $color = null; // 验证码字体颜色 |
|||
|
|||
/** |
|||
* @var Config|null |
|||
*/ |
|||
private $config = null; |
|||
|
|||
/** |
|||
* @var Session|null |
|||
*/ |
|||
private $session = null; |
|||
|
|||
// 验证码字符集合 |
|||
protected $codeSet = '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY'; |
|||
// 验证码过期时间(s) |
|||
protected $expire = 1800; |
|||
// 使用中文验证码 |
|||
protected $useZh = false; |
|||
// 中文验证码字符串 |
|||
protected $zhSet = '们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书术状厂须离再目海交权且儿青才证低越际八试规斯近注办布门铁需走议县兵固除般引齿千胜细影济白格效置推空配刀叶率述今选养德话查差半敌始片施响收华觉备名红续均药标记难存测士身紧液派准斤角降维板许破述技消底床田势端感往神便贺村构照容非搞亚磨族火段算适讲按值美态黄易彪服早班麦削信排台声该击素张密害侯草何树肥继右属市严径螺检左页抗苏显苦英快称坏移约巴材省黑武培著河帝仅针怎植京助升王眼她抓含苗副杂普谈围食射源例致酸旧却充足短划剂宣环落首尺波承粉践府鱼随考刻靠够满夫失包住促枝局菌杆周护岩师举曲春元超负砂封换太模贫减阳扬江析亩木言球朝医校古呢稻宋听唯输滑站另卫字鼓刚写刘微略范供阿块某功套友限项余倒卷创律雨让骨远帮初皮播优占死毒圈伟季训控激找叫云互跟裂粮粒母练塞钢顶策双留误础吸阻故寸盾晚丝女散焊功株亲院冷彻弹错散商视艺灭版烈零室轻血倍缺厘泵察绝富城冲喷壤简否柱李望盘磁雄似困巩益洲脱投送奴侧润盖挥距触星松送获兴独官混纪依未突架宽冬章湿偏纹吃执阀矿寨责熟稳夺硬价努翻奇甲预职评读背协损棉侵灰虽矛厚罗泥辟告卵箱掌氧恩爱停曾溶营终纲孟钱待尽俄缩沙退陈讨奋械载胞幼哪剥迫旋征槽倒握担仍呀鲜吧卡粗介钻逐弱脚怕盐末阴丰雾冠丙街莱贝辐肠付吉渗瑞惊顿挤秒悬姆烂森糖圣凹陶词迟蚕亿矩康遵牧遭幅园腔订香肉弟屋敏恢忘编印蜂急拿扩伤飞露核缘游振操央伍域甚迅辉异序免纸夜乡久隶缸夹念兰映沟乙吗儒杀汽磷艰晶插埃燃欢铁补咱芽永瓦倾阵碳演威附牙芽永瓦斜灌欧献顺猪洋腐请透司危括脉宜笑若尾束壮暴企菜穗楚汉愈绿拖牛份染既秋遍锻玉夏疗尖殖井费州访吹荣铜沿替滚客召旱悟刺脑措贯藏敢令隙炉壳硫煤迎铸粘探临薄旬善福纵择礼愿伏残雷延烟句纯渐耕跑泽慢栽鲁赤繁境潮横掉锥希池败船假亮谓托伙哲怀割摆贡呈劲财仪沉炼麻罪祖息车穿货销齐鼠抽画饲龙库守筑房歌寒喜哥洗蚀废纳腹乎录镜妇恶脂庄擦险赞钟摇典柄辩竹谷卖乱虚桥奥伯赶垂途额壁网截野遗静谋弄挂课镇妄盛耐援扎虑键归符庆聚绕摩忙舞遇索顾胶羊湖钉仁音迹碎伸灯避泛亡答勇频皇柳哈揭甘诺概宪浓岛袭谁洪谢炮浇斑讯懂灵蛋闭孩释乳巨徒私银伊景坦累匀霉杜乐勒隔弯绩招绍胡呼痛峰零柴簧午跳居尚丁秦稍追梁折耗碱殊岗挖氏刃剧堆赫荷胸衡勤膜篇登驻案刊秧缓凸役剪川雪链渔啦脸户洛孢勃盟买杨宗焦赛旗滤硅炭股坐蒸凝竟陷枪黎救冒暗洞犯筒您宋弧爆谬涂味津臂障褐陆啊健尊豆拔莫抵桑坡缝警挑污冰柬嘴啥饭塑寄赵喊垫丹渡耳刨虎笔稀昆浪萨茶滴浅拥穴覆伦娘吨浸袖珠雌妈紫戏塔锤震岁貌洁剖牢锋疑霸闪埔猛诉刷狠忽灾闹乔唐漏闻沈熔氯荒茎男凡抢像浆旁玻亦忠唱蒙予纷捕锁尤乘乌智淡允叛畜俘摸锈扫毕璃宝芯爷鉴秘净蒋钙肩腾枯抛轨堂拌爸循诱祝励肯酒绳穷塘燥泡袋朗喂铝软渠颗惯贸粪综墙趋彼届墨碍启逆卸航衣孙龄岭骗休借'; |
|||
// 使用背景图片 |
|||
protected $useImgBg = false; |
|||
// 验证码字体大小(px) |
|||
protected $fontSize = 25; |
|||
// 是否画混淆曲线 |
|||
protected $useCurve = true; |
|||
// 是否添加杂点 |
|||
protected $useNoise = true; |
|||
// 验证码图片高度 |
|||
protected $imageH = 0; |
|||
// 验证码图片宽度 |
|||
protected $imageW = 0; |
|||
// 验证码位数 |
|||
protected $length = 5; |
|||
// 验证码字体,不设置随机获取 |
|||
protected $fontttf = ''; |
|||
// 背景颜色 |
|||
protected $bg = [243, 251, 254]; |
|||
//算术验证码 |
|||
protected $math = false; |
|||
|
|||
/** |
|||
* 架构方法 设置参数 |
|||
* @access public |
|||
* @param Config $config |
|||
* @param Cache $cache |
|||
*/ |
|||
public function __construct(Config $config, Cache $cache) |
|||
{ |
|||
$this->config = $config; |
|||
$this->cache = $cache; |
|||
} |
|||
|
|||
/** |
|||
* 配置验证码 |
|||
* @param string|null $config |
|||
*/ |
|||
protected function configure(string $config = null): void |
|||
{ |
|||
if (is_null($config)) { |
|||
$config = $this->config->get('captcha', []); |
|||
} else { |
|||
$config = $this->config->get('captcha.' . $config, []); |
|||
} |
|||
|
|||
foreach ($config as $key => $val) { |
|||
if (property_exists($this, $key)) { |
|||
$this->{$key} = $val; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 创建验证码 |
|||
* @return array |
|||
* @throws Exception |
|||
*/ |
|||
protected function generate(): array |
|||
{ |
|||
$bag = ''; |
|||
|
|||
if ($this->math) { |
|||
$this->useZh = false; |
|||
$this->length = 5; |
|||
|
|||
$x = random_int(10, 30); |
|||
$y = random_int(1, 9); |
|||
$bag = "{$x} + {$y} = "; |
|||
$key = $x + $y; |
|||
$key .= ''; |
|||
} else { |
|||
if ($this->useZh) { |
|||
$characters = preg_split('/(?<!^)(?!$)/u', $this->zhSet); |
|||
} else { |
|||
$characters = str_split($this->codeSet); |
|||
} |
|||
|
|||
for ($i = 0; $i < $this->length; $i++) { |
|||
$bag .= $characters[rand(0, count($characters) - 1)]; |
|||
} |
|||
|
|||
$key = mb_strtolower($bag, 'UTF-8'); |
|||
} |
|||
|
|||
$hash = password_hash($key, PASSWORD_BCRYPT, ['cost' => 10]); |
|||
$this->cache->set('captchaApi.' . $hash, $key, $this->expire); |
|||
|
|||
return [ |
|||
'value' => $bag, |
|||
'key' => $hash, |
|||
'code' => $key, |
|||
'md5' => md5($key), |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* 输出验证码并把验证码的值保存的缓存中 |
|||
* @access public |
|||
* @param null|string $config |
|||
* @param bool $api |
|||
* @return object |
|||
*/ |
|||
public function createSMS(string $phone = null): array |
|||
{ |
|||
$key = rand(1000, 9999); |
|||
$this->cache->set('captchaSMS.' . $phone, $key, $this->expire); |
|||
return [ |
|||
'key' => $phone, |
|||
'code' => $key, |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* 输出验证码并把验证码的值保存的缓存中 |
|||
* @access public |
|||
* @param null|string $config |
|||
* @param bool $api |
|||
* @return object |
|||
*/ |
|||
public function checkSMS(string $code, string $phone): bool |
|||
{ |
|||
$res = false; |
|||
if ($this->cache->get('captchaSMS.' . $phone) == $code) { |
|||
$res = true; |
|||
} |
|||
|
|||
if ($res) { |
|||
$this->cache->delete('captchaSMS.' . $phone); |
|||
} |
|||
|
|||
return $res; |
|||
} |
|||
|
|||
/** |
|||
* 验证验证码是否正确 |
|||
* @access public |
|||
* @param string $code 用户验证码 |
|||
* @param string $key 用户验证码key |
|||
* @return bool 用户验证码是否正确 |
|||
*/ |
|||
public function check(string $code, string $hash): bool |
|||
{ |
|||
$res = false; |
|||
if ($this->cache->get('captchaApi.' . $hash)) { |
|||
$code = mb_strtolower($code, 'UTF-8'); |
|||
$res = password_verify($code, $hash); |
|||
} |
|||
|
|||
if ($res) { |
|||
$this->cache->delete('captchaApi.' . $hash); |
|||
} |
|||
|
|||
return $res; |
|||
} |
|||
|
|||
/** |
|||
* 输出验证码并把验证码的值保存的缓存中 |
|||
* @access public |
|||
* @param null|string $config |
|||
* @param bool $api |
|||
* @return object |
|||
*/ |
|||
public function create(string $config = null, bool $api = false): array |
|||
{ |
|||
$this->configure($config); |
|||
|
|||
$generator = $this->generate(); |
|||
|
|||
// 图片宽(px) |
|||
$this->imageW || $this->imageW = $this->length * $this->fontSize * 1.5 + $this->length * $this->fontSize / 2; |
|||
// 图片高(px) |
|||
$this->imageH || $this->imageH = $this->fontSize * 2.5; |
|||
// 建立一幅 $this->imageW x $this->imageH 的图像 |
|||
$this->im = imagecreate($this->imageW, $this->imageH); |
|||
// 设置背景 |
|||
imagecolorallocate($this->im, $this->bg[0], $this->bg[1], $this->bg[2]); |
|||
|
|||
// 验证码字体随机颜色 |
|||
$this->color = imagecolorallocate($this->im, mt_rand(1, 150), mt_rand(1, 150), mt_rand(1, 150)); |
|||
|
|||
// 验证码使用随机字体 |
|||
$ttfPath = __DIR__ . '/../assets/' . ($this->useZh ? 'zhttfs' : 'ttfs') . '/'; |
|||
|
|||
if (empty($this->fontttf)) { |
|||
$dir = dir($ttfPath); |
|||
$ttfs = []; |
|||
while (false !== ($file = $dir->read())) { |
|||
if ('.' != $file[0] && substr($file, -4) == '.ttf') { |
|||
$ttfs[] = $file; |
|||
} |
|||
} |
|||
$dir->close(); |
|||
$this->fontttf = $ttfs[array_rand($ttfs)]; |
|||
} |
|||
|
|||
$fontttf = $ttfPath . $this->fontttf; |
|||
|
|||
if ($this->useImgBg) { |
|||
$this->background(); |
|||
} |
|||
|
|||
if ($this->useNoise) { |
|||
// 绘杂点 |
|||
$this->writeNoise(); |
|||
} |
|||
if ($this->useCurve) { |
|||
// 绘干扰线 |
|||
$this->writeCurve(); |
|||
} |
|||
|
|||
// 绘验证码 |
|||
$text = $this->useZh ? preg_split('/(?<!^)(?!$)/u', $generator['value']) : str_split($generator['value']); // 验证码 |
|||
|
|||
foreach ($text as $index => $char) { |
|||
|
|||
$x = $this->fontSize * ($index + 1) * mt_rand(1.2, 1.6) * ($this->math ? 1 : 1.5); |
|||
$y = $this->fontSize + mt_rand(10, 20); |
|||
$angle = $this->math ? 0 : mt_rand(-40, 40); |
|||
|
|||
imagettftext($this->im, $this->fontSize, $angle, $x, $y, $this->color, $fontttf, $char); |
|||
} |
|||
|
|||
ob_start(); |
|||
// 输出图像 |
|||
imagepng($this->im); |
|||
$content = ob_get_clean(); |
|||
imagedestroy($this->im); |
|||
return [ |
|||
'base64' => 'data:image/png;base64,' . chunk_split(base64_encode($content)), |
|||
'key' => $generator['key'], |
|||
'code' => $generator['code'], |
|||
'md5' => $generator['md5'], |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数) |
|||
* |
|||
* 高中的数学公式咋都忘了涅,写出来 |
|||
* 正弦型函数解析式:y=Asin(ωx+φ)+b |
|||
* 各常数值对函数图像的影响: |
|||
* A:决定峰值(即纵向拉伸压缩的倍数) |
|||
* b:表示波形在Y轴的位置关系或纵向移动距离(上加下减) |
|||
* φ:决定波形与X轴位置关系或横向移动距离(左加右减) |
|||
* ω:决定周期(最小正周期T=2π/∣ω∣) |
|||
* |
|||
*/ |
|||
protected function writeCurve(): void |
|||
{ |
|||
$px = $py = 0; |
|||
|
|||
// 曲线前部分 |
|||
$A = mt_rand(1, $this->imageH / 2); // 振幅 |
|||
$b = mt_rand(-$this->imageH / 4, $this->imageH / 4); // Y轴方向偏移量 |
|||
$f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量 |
|||
$T = mt_rand($this->imageH, $this->imageW * 2); // 周期 |
|||
$w = (2 * M_PI) / $T; |
|||
|
|||
$px1 = 0; // 曲线横坐标起始位置 |
|||
$px2 = mt_rand($this->imageW / 2, $this->imageW * 0.8); // 曲线横坐标结束位置 |
|||
|
|||
for ($px = $px1; $px <= $px2; $px = $px + 1) { |
|||
if (0 != $w) { |
|||
$py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b |
|||
$i = (int)($this->fontSize / 5); |
|||
while ($i > 0) { |
|||
imagesetpixel($this->im, $px + $i, $py + $i, $this->color); // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出(不用这while循环)性能要好很多 |
|||
$i--; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 曲线后部分 |
|||
$A = mt_rand(1, $this->imageH / 2); // 振幅 |
|||
$f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量 |
|||
$T = mt_rand($this->imageH, $this->imageW * 2); // 周期 |
|||
$w = (2 * M_PI) / $T; |
|||
$b = $py - $A * sin($w * $px + $f) - $this->imageH / 2; |
|||
$px1 = $px2; |
|||
$px2 = $this->imageW; |
|||
|
|||
for ($px = $px1; $px <= $px2; $px = $px + 1) { |
|||
if (0 != $w) { |
|||
$py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b |
|||
$i = (int)($this->fontSize / 5); |
|||
while ($i > 0) { |
|||
imagesetpixel($this->im, $px + $i, $py + $i, $this->color); |
|||
$i--; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 画杂点 |
|||
* 往图片上写不同颜色的字母或数字 |
|||
*/ |
|||
protected function writeNoise(): void |
|||
{ |
|||
$codeSet = '2345678abcdefhijkmnpqrstuvwxyz'; |
|||
for ($i = 0; $i < 10; $i++) { |
|||
//杂点颜色 |
|||
$noiseColor = imagecolorallocate($this->im, mt_rand(150, 225), mt_rand(150, 225), mt_rand(150, 225)); |
|||
for ($j = 0; $j < 5; $j++) { |
|||
// 绘杂点 |
|||
imagestring($this->im, 5, mt_rand(-10, $this->imageW), mt_rand(-10, $this->imageH), $codeSet[mt_rand(0, 29)], $noiseColor); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 绘制背景图片 |
|||
* 注:如果验证码输出图片比较大,将占用比较多的系统资源 |
|||
*/ |
|||
protected function background(): void |
|||
{ |
|||
$path = __DIR__ . '/../assets/bgs/'; |
|||
$dir = dir($path); |
|||
|
|||
$bgs = []; |
|||
while (false !== ($file = $dir->read())) { |
|||
if ('.' != $file[0] && substr($file, -4) == '.jpg') { |
|||
$bgs[] = $path . $file; |
|||
} |
|||
} |
|||
$dir->close(); |
|||
|
|||
$gb = $bgs[array_rand($bgs)]; |
|||
|
|||
list($width, $height) = @getimagesize($gb); |
|||
// Resample |
|||
$bgImage = @imagecreatefromjpeg($gb); |
|||
@imagecopyresampled($this->im, $bgImage, 0, 0, 0, 0, $this->imageW, $this->imageH, $width, $height); |
|||
@imagedestroy($bgImage); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace edward\captcha; |
|||
|
|||
class CaptchaController |
|||
{ |
|||
public function index(Captcha $captcha, $config = null) |
|||
{ |
|||
return $captcha->create($config); |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
<?php |
|||
|
|||
namespace edward\captcha; |
|||
|
|||
use think\Route; |
|||
use think\Service; |
|||
use think\Validate; |
|||
|
|||
class CaptchaService extends Service |
|||
{ |
|||
public function boot() |
|||
{ |
|||
Validate::maker(function ($validate) { |
|||
$validate->extend('captcha', function ($value) { |
|||
return captcha_check($value); |
|||
}, ':attribute错误!'); |
|||
}); |
|||
|
|||
$this->registerRoutes(function (Route $route) { |
|||
$route->get('captcha/[:config]', "\\edward\\captcha\\CaptchaController@index"); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Captcha配置文件 |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
return [ |
|||
//验证码位数 |
|||
'length' => 5, |
|||
// 验证码字符集合 |
|||
'codeSet' => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY', |
|||
// 验证码过期时间 |
|||
'expire' => 1800, |
|||
// 是否使用中文验证码 |
|||
'useZh' => false, |
|||
// 是否使用算术验证码 |
|||
'math' => false, |
|||
// 是否使用背景图 |
|||
'useImgBg' => false, |
|||
//验证码字符大小 |
|||
'fontSize' => 25, |
|||
// 是否使用混淆曲线 |
|||
'useCurve' => true, |
|||
//是否添加杂点 |
|||
'useNoise' => true, |
|||
// 验证码字体 不设置则随机 |
|||
'fontttf' => '', |
|||
//背景颜色 |
|||
'bg' => [243, 251, 254], |
|||
// 验证码图片高度 |
|||
'imageH' => 0, |
|||
// 验证码图片宽度 |
|||
'imageW' => 0, |
|||
|
|||
// 添加额外的验证码设置 |
|||
// verify => [ |
|||
// 'length'=>4, |
|||
// ... |
|||
//], |
|||
]; |
|||
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
namespace edward\captcha\facade; |
|||
|
|||
use think\Facade; |
|||
|
|||
/** |
|||
* Class Captcha |
|||
* @package edward\captcha\facade |
|||
* @mixin \edward\captcha\Captcha |
|||
*/ |
|||
class Captcha extends Facade |
|||
{ |
|||
protected static function getFacadeClass() |
|||
{ |
|||
return \edward\captcha\Captcha::class; |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
namespace edward\captcha\facade; |
|||
|
|||
use think\Facade; |
|||
|
|||
/** |
|||
* Class Captcha |
|||
* @package edward\captcha\facade |
|||
* @mixin \edward\captcha\CaptchaApi |
|||
*/ |
|||
class CaptchaApi extends Facade |
|||
{ |
|||
protected static function getFacadeClass() |
|||
{ |
|||
return \edward\captcha\CaptchaApi::class; |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
use edward\captcha\facade\Captcha; |
|||
use think\facade\Route; |
|||
use think\Response; |
|||
|
|||
/** |
|||
* @param string $config |
|||
* @return \think\Response |
|||
*/ |
|||
function captcha($config = null): Response |
|||
{ |
|||
return Captcha::create($config); |
|||
} |
|||
|
|||
/** |
|||
* @param $config |
|||
* @return string |
|||
*/ |
|||
function captcha_src($config = null): string |
|||
{ |
|||
return Route::buildUrl('/captcha' . ($config ? "/{$config}" : '')); |
|||
} |
|||
|
|||
/** |
|||
* @param $id |
|||
* @return string |
|||
*/ |
|||
function captcha_img($id = '', $domid = ''): string |
|||
{ |
|||
$src = captcha_src($id); |
|||
|
|||
$domid = empty($domid) ? $domid : "id='" . $domid . "'"; |
|||
|
|||
return "<img src='{$src}' alt='captcha' " . $domid . " onclick='this.src=\"{$src}?\"+Math.random();' />"; |
|||
} |
|||
|
|||
/** |
|||
* @param string $value |
|||
* @return bool |
|||
*/ |
|||
function captcha_check($value) |
|||
{ |
|||
return Captcha::check($value); |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
Copyright (c) 2013-2016 Eduardo Gulias Davis |
|||
|
|||
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. |
|||
@ -0,0 +1,38 @@ |
|||
{ |
|||
"name": "egulias/email-validator", |
|||
"description": "A library for validating emails against several RFCs", |
|||
"homepage": "https://github.com/egulias/EmailValidator", |
|||
"keywords": ["email", "validation", "validator", "emailvalidation", "emailvalidator"], |
|||
"license": "MIT", |
|||
"authors": [ |
|||
{"name": "Eduardo Gulias Davis"} |
|||
], |
|||
"extra": { |
|||
"branch-alias": { |
|||
"dev-master": "2.1.x-dev" |
|||
} |
|||
}, |
|||
"require": { |
|||
"php": ">=5.5", |
|||
"doctrine/lexer": "^1.0.1", |
|||
"symfony/polyfill-intl-idn": "^1.10" |
|||
}, |
|||
"require-dev": { |
|||
"dominicsayers/isemail": "^3.0.7", |
|||
"phpunit/phpunit": "^4.8.36|^7.5.15", |
|||
"satooshi/php-coveralls": "^1.0.1" |
|||
}, |
|||
"suggest": { |
|||
"ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" |
|||
}, |
|||
"autoload": { |
|||
"psr-4": { |
|||
"Egulias\\EmailValidator\\": "src" |
|||
} |
|||
}, |
|||
"autoload-dev": { |
|||
"psr-4": { |
|||
"Egulias\\EmailValidator\\Tests\\": "tests" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,283 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator; |
|||
|
|||
use Doctrine\Common\Lexer\AbstractLexer; |
|||
|
|||
class EmailLexer extends AbstractLexer |
|||
{ |
|||
//ASCII values |
|||
const C_DEL = 127; |
|||
const C_NUL = 0; |
|||
const S_AT = 64; |
|||
const S_BACKSLASH = 92; |
|||
const S_DOT = 46; |
|||
const S_DQUOTE = 34; |
|||
const S_SQUOTE = 39; |
|||
const S_BACKTICK = 96; |
|||
const S_OPENPARENTHESIS = 49; |
|||
const S_CLOSEPARENTHESIS = 261; |
|||
const S_OPENBRACKET = 262; |
|||
const S_CLOSEBRACKET = 263; |
|||
const S_HYPHEN = 264; |
|||
const S_COLON = 265; |
|||
const S_DOUBLECOLON = 266; |
|||
const S_SP = 267; |
|||
const S_HTAB = 268; |
|||
const S_CR = 269; |
|||
const S_LF = 270; |
|||
const S_IPV6TAG = 271; |
|||
const S_LOWERTHAN = 272; |
|||
const S_GREATERTHAN = 273; |
|||
const S_COMMA = 274; |
|||
const S_SEMICOLON = 275; |
|||
const S_OPENQBRACKET = 276; |
|||
const S_CLOSEQBRACKET = 277; |
|||
const S_SLASH = 278; |
|||
const S_EMPTY = null; |
|||
const GENERIC = 300; |
|||
const CRLF = 301; |
|||
const INVALID = 302; |
|||
const ASCII_INVALID_FROM = 127; |
|||
const ASCII_INVALID_TO = 199; |
|||
|
|||
/** |
|||
* US-ASCII visible characters not valid for atext (@link http://tools.ietf.org/html/rfc5322#section-3.2.3) |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $charValue = array( |
|||
'(' => self::S_OPENPARENTHESIS, |
|||
')' => self::S_CLOSEPARENTHESIS, |
|||
'<' => self::S_LOWERTHAN, |
|||
'>' => self::S_GREATERTHAN, |
|||
'[' => self::S_OPENBRACKET, |
|||
']' => self::S_CLOSEBRACKET, |
|||
':' => self::S_COLON, |
|||
';' => self::S_SEMICOLON, |
|||
'@' => self::S_AT, |
|||
'\\' => self::S_BACKSLASH, |
|||
'/' => self::S_SLASH, |
|||
',' => self::S_COMMA, |
|||
'.' => self::S_DOT, |
|||
"'" => self::S_SQUOTE, |
|||
"`" => self::S_BACKTICK, |
|||
'"' => self::S_DQUOTE, |
|||
'-' => self::S_HYPHEN, |
|||
'::' => self::S_DOUBLECOLON, |
|||
' ' => self::S_SP, |
|||
"\t" => self::S_HTAB, |
|||
"\r" => self::S_CR, |
|||
"\n" => self::S_LF, |
|||
"\r\n" => self::CRLF, |
|||
'IPv6' => self::S_IPV6TAG, |
|||
'{' => self::S_OPENQBRACKET, |
|||
'}' => self::S_CLOSEQBRACKET, |
|||
'' => self::S_EMPTY, |
|||
'\0' => self::C_NUL, |
|||
); |
|||
|
|||
/** |
|||
* @var bool |
|||
*/ |
|||
protected $hasInvalidTokens = false; |
|||
|
|||
/** |
|||
* @var array |
|||
* |
|||
* @psalm-var array{value:string, type:null|int, position:int}|array<empty, empty> |
|||
*/ |
|||
protected $previous = []; |
|||
|
|||
/** |
|||
* The last matched/seen token. |
|||
* |
|||
* @var array |
|||
* |
|||
* @psalm-var array{value:string, type:null|int, position:int} |
|||
*/ |
|||
public $token; |
|||
|
|||
/** |
|||
* The next token in the input. |
|||
* |
|||
* @var array|null |
|||
*/ |
|||
public $lookahead; |
|||
|
|||
/** |
|||
* @psalm-var array{value:'', type:null, position:0} |
|||
*/ |
|||
private static $nullToken = [ |
|||
'value' => '', |
|||
'type' => null, |
|||
'position' => 0, |
|||
]; |
|||
|
|||
public function __construct() |
|||
{ |
|||
$this->previous = $this->token = self::$nullToken; |
|||
$this->lookahead = null; |
|||
} |
|||
|
|||
/** |
|||
* @return void |
|||
*/ |
|||
public function reset() |
|||
{ |
|||
$this->hasInvalidTokens = false; |
|||
parent::reset(); |
|||
$this->previous = $this->token = self::$nullToken; |
|||
} |
|||
|
|||
/** |
|||
* @return bool |
|||
*/ |
|||
public function hasInvalidTokens() |
|||
{ |
|||
return $this->hasInvalidTokens; |
|||
} |
|||
|
|||
/** |
|||
* @param int $type |
|||
* @throws \UnexpectedValueException |
|||
* @return boolean |
|||
* |
|||
* @psalm-suppress InvalidScalarArgument |
|||
*/ |
|||
public function find($type) |
|||
{ |
|||
$search = clone $this; |
|||
$search->skipUntil($type); |
|||
|
|||
if (!$search->lookahead) { |
|||
throw new \UnexpectedValueException($type . ' not found'); |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* getPrevious |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function getPrevious() |
|||
{ |
|||
return $this->previous; |
|||
} |
|||
|
|||
/** |
|||
* moveNext |
|||
* |
|||
* @return boolean |
|||
*/ |
|||
public function moveNext() |
|||
{ |
|||
$this->previous = $this->token; |
|||
$hasNext = parent::moveNext(); |
|||
$this->token = $this->token ?: self::$nullToken; |
|||
|
|||
return $hasNext; |
|||
} |
|||
|
|||
/** |
|||
* Lexical catchable patterns. |
|||
* |
|||
* @return string[] |
|||
*/ |
|||
protected function getCatchablePatterns() |
|||
{ |
|||
return array( |
|||
'[a-zA-Z_]+[46]?', //ASCII and domain literal |
|||
'[^\x00-\x7F]', //UTF-8 |
|||
'[0-9]+', |
|||
'\r\n', |
|||
'::', |
|||
'\s+?', |
|||
'.', |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Lexical non-catchable patterns. |
|||
* |
|||
* @return string[] |
|||
*/ |
|||
protected function getNonCatchablePatterns() |
|||
{ |
|||
return array('[\xA0-\xff]+'); |
|||
} |
|||
|
|||
/** |
|||
* Retrieve token type. Also processes the token value if necessary. |
|||
* |
|||
* @param string $value |
|||
* @throws \InvalidArgumentException |
|||
* @return integer |
|||
*/ |
|||
protected function getType(&$value) |
|||
{ |
|||
if ($this->isNullType($value)) { |
|||
return self::C_NUL; |
|||
} |
|||
|
|||
if ($this->isValid($value)) { |
|||
return $this->charValue[$value]; |
|||
} |
|||
|
|||
if ($this->isUTF8Invalid($value)) { |
|||
$this->hasInvalidTokens = true; |
|||
return self::INVALID; |
|||
} |
|||
|
|||
return self::GENERIC; |
|||
} |
|||
|
|||
/** |
|||
* @param string $value |
|||
* |
|||
* @return bool |
|||
*/ |
|||
protected function isValid($value) |
|||
{ |
|||
if (isset($this->charValue[$value])) { |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* @param string $value |
|||
* @return bool |
|||
*/ |
|||
protected function isNullType($value) |
|||
{ |
|||
if ($value === "\0") { |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* @param string $value |
|||
* @return bool |
|||
*/ |
|||
protected function isUTF8Invalid($value) |
|||
{ |
|||
if (preg_match('/\p{Cc}+/u', $value)) { |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* @return string |
|||
*/ |
|||
protected function getModifiers() |
|||
{ |
|||
return 'iu'; |
|||
} |
|||
} |
|||
@ -0,0 +1,137 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator; |
|||
|
|||
use Egulias\EmailValidator\Exception\ExpectingATEXT; |
|||
use Egulias\EmailValidator\Exception\NoLocalPart; |
|||
use Egulias\EmailValidator\Parser\DomainPart; |
|||
use Egulias\EmailValidator\Parser\LocalPart; |
|||
use Egulias\EmailValidator\Warning\EmailTooLong; |
|||
|
|||
/** |
|||
* EmailParser |
|||
* |
|||
* @author Eduardo Gulias Davis <me@egulias.com> |
|||
*/ |
|||
class EmailParser |
|||
{ |
|||
const EMAIL_MAX_LENGTH = 254; |
|||
|
|||
/** |
|||
* @var array |
|||
*/ |
|||
protected $warnings = []; |
|||
|
|||
/** |
|||
* @var string |
|||
*/ |
|||
protected $domainPart = ''; |
|||
|
|||
/** |
|||
* @var string |
|||
*/ |
|||
protected $localPart = ''; |
|||
/** |
|||
* @var EmailLexer |
|||
*/ |
|||
protected $lexer; |
|||
|
|||
/** |
|||
* @var LocalPart |
|||
*/ |
|||
protected $localPartParser; |
|||
|
|||
/** |
|||
* @var DomainPart |
|||
*/ |
|||
protected $domainPartParser; |
|||
|
|||
public function __construct(EmailLexer $lexer) |
|||
{ |
|||
$this->lexer = $lexer; |
|||
$this->localPartParser = new LocalPart($this->lexer); |
|||
$this->domainPartParser = new DomainPart($this->lexer); |
|||
} |
|||
|
|||
/** |
|||
* @param string $str |
|||
* @return array |
|||
*/ |
|||
public function parse($str) |
|||
{ |
|||
$this->lexer->setInput($str); |
|||
|
|||
if (!$this->hasAtToken()) { |
|||
throw new NoLocalPart(); |
|||
} |
|||
|
|||
|
|||
$this->localPartParser->parse($str); |
|||
$this->domainPartParser->parse($str); |
|||
|
|||
$this->setParts($str); |
|||
|
|||
if ($this->lexer->hasInvalidTokens()) { |
|||
throw new ExpectingATEXT(); |
|||
} |
|||
|
|||
return array('local' => $this->localPart, 'domain' => $this->domainPart); |
|||
} |
|||
|
|||
/** |
|||
* @return Warning\Warning[] |
|||
*/ |
|||
public function getWarnings() |
|||
{ |
|||
$localPartWarnings = $this->localPartParser->getWarnings(); |
|||
$domainPartWarnings = $this->domainPartParser->getWarnings(); |
|||
$this->warnings = array_merge($localPartWarnings, $domainPartWarnings); |
|||
|
|||
$this->addLongEmailWarning($this->localPart, $this->domainPart); |
|||
|
|||
return $this->warnings; |
|||
} |
|||
|
|||
/** |
|||
* @return string |
|||
*/ |
|||
public function getParsedDomainPart() |
|||
{ |
|||
return $this->domainPart; |
|||
} |
|||
|
|||
/** |
|||
* @param string $email |
|||
*/ |
|||
protected function setParts($email) |
|||
{ |
|||
$parts = explode('@', $email); |
|||
$this->domainPart = $this->domainPartParser->getDomainPart(); |
|||
$this->localPart = $parts[0]; |
|||
} |
|||
|
|||
/** |
|||
* @return bool |
|||
*/ |
|||
protected function hasAtToken() |
|||
{ |
|||
$this->lexer->moveNext(); |
|||
$this->lexer->moveNext(); |
|||
if ($this->lexer->token['type'] === EmailLexer::S_AT) { |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* @param string $localPart |
|||
* @param string $parsedDomainPart |
|||
*/ |
|||
protected function addLongEmailWarning($localPart, $parsedDomainPart) |
|||
{ |
|||
if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAIL_MAX_LENGTH) { |
|||
$this->warnings[EmailTooLong::CODE] = new EmailTooLong(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator; |
|||
|
|||
use Egulias\EmailValidator\Exception\InvalidEmail; |
|||
use Egulias\EmailValidator\Validation\EmailValidation; |
|||
|
|||
class EmailValidator |
|||
{ |
|||
/** |
|||
* @var EmailLexer |
|||
*/ |
|||
private $lexer; |
|||
|
|||
/** |
|||
* @var Warning\Warning[] |
|||
*/ |
|||
protected $warnings = []; |
|||
|
|||
/** |
|||
* @var InvalidEmail|null |
|||
*/ |
|||
protected $error; |
|||
|
|||
public function __construct() |
|||
{ |
|||
$this->lexer = new EmailLexer(); |
|||
} |
|||
|
|||
/** |
|||
* @param string $email |
|||
* @param EmailValidation $emailValidation |
|||
* @return bool |
|||
*/ |
|||
public function isValid($email, EmailValidation $emailValidation) |
|||
{ |
|||
$isValid = $emailValidation->isValid($email, $this->lexer); |
|||
$this->warnings = $emailValidation->getWarnings(); |
|||
$this->error = $emailValidation->getError(); |
|||
|
|||
return $isValid; |
|||
} |
|||
|
|||
/** |
|||
* @return boolean |
|||
*/ |
|||
public function hasWarnings() |
|||
{ |
|||
return !empty($this->warnings); |
|||
} |
|||
|
|||
/** |
|||
* @return array |
|||
*/ |
|||
public function getWarnings() |
|||
{ |
|||
return $this->warnings; |
|||
} |
|||
|
|||
/** |
|||
* @return InvalidEmail|null |
|||
*/ |
|||
public function getError() |
|||
{ |
|||
return $this->error; |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator\Exception; |
|||
|
|||
class AtextAfterCFWS extends InvalidEmail |
|||
{ |
|||
const CODE = 133; |
|||
const REASON = "ATEXT found after CFWS"; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator\Exception; |
|||
|
|||
class CRLFAtTheEnd extends InvalidEmail |
|||
{ |
|||
const CODE = 149; |
|||
const REASON = "CRLF at the end"; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator\Exception; |
|||
|
|||
class CRLFX2 extends InvalidEmail |
|||
{ |
|||
const CODE = 148; |
|||
const REASON = "Folding whitespace CR LF found twice"; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator\Exception; |
|||
|
|||
class CRNoLF extends InvalidEmail |
|||
{ |
|||
const CODE = 150; |
|||
const REASON = "Missing LF after CR"; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator\Exception; |
|||
|
|||
class CharNotAllowed extends InvalidEmail |
|||
{ |
|||
const CODE = 201; |
|||
const REASON = "Non allowed character in domain"; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator\Exception; |
|||
|
|||
class CommaInDomain extends InvalidEmail |
|||
{ |
|||
const CODE = 200; |
|||
const REASON = "Comma ',' is not allowed in domain part"; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator\Exception; |
|||
|
|||
class ConsecutiveAt extends InvalidEmail |
|||
{ |
|||
const CODE = 128; |
|||
const REASON = "Consecutive AT"; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator\Exception; |
|||
|
|||
class ConsecutiveDot extends InvalidEmail |
|||
{ |
|||
const CODE = 132; |
|||
const REASON = "Consecutive DOT"; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator\Exception; |
|||
|
|||
class DomainAcceptsNoMail extends InvalidEmail |
|||
{ |
|||
const CODE = 154; |
|||
const REASON = 'Domain accepts no mail (Null MX, RFC7505)'; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator\Exception; |
|||
|
|||
class DomainHyphened extends InvalidEmail |
|||
{ |
|||
const CODE = 144; |
|||
const REASON = "Hyphen found in domain"; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator\Exception; |
|||
|
|||
class DotAtEnd extends InvalidEmail |
|||
{ |
|||
const CODE = 142; |
|||
const REASON = "Dot at the end"; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator\Exception; |
|||
|
|||
class DotAtStart extends InvalidEmail |
|||
{ |
|||
const CODE = 141; |
|||
const REASON = "Found DOT at start"; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
namespace Egulias\EmailValidator\Exception; |
|||
|
|||
class ExpectingAT extends InvalidEmail |
|||
{ |
|||
const CODE = 202; |
|||
const REASON = "Expecting AT '@' "; |
|||
} |
|||