update and video tag

This commit is contained in:
merdan 2021-04-16 14:15:27 +05:00
parent 5d7ce1bf03
commit b4667f1975
505 changed files with 8351 additions and 8457 deletions

View File

@ -234,8 +234,8 @@ class UpdateManager
$params = [
'core' => $this->getHash(),
'plugins' => serialize($versions),
'themes' => serialize($themes),
'plugins' => base64_encode(json_encode($versions)),
'themes' => base64_encode(json_encode($themes)),
'build' => $build,
'force' => $force
];
@ -590,8 +590,9 @@ class UpdateManager
{
$fileCode = $name . $hash;
$filePath = $this->getFilePath($fileCode);
$innerPath = str_replace('.', '/', strtolower($name));
if (!Zip::extract($filePath, plugins_path())) {
if (!Zip::extract($filePath, plugins_path($innerPath))) {
throw new ApplicationException(Lang::get('system::lang.zip.extract_failed', ['file' => $filePath]));
}
@ -632,8 +633,9 @@ class UpdateManager
{
$fileCode = $name . $hash;
$filePath = $this->getFilePath($fileCode);
$innerPath = str_replace('.', '-', strtolower($name));
if (!Zip::extract($filePath, themes_path())) {
if (!Zip::extract($filePath, themes_path($innerPath))) {
throw new ApplicationException(Lang::get('system::lang.zip.extract_failed', ['file' => $filePath]));
}
@ -903,13 +905,16 @@ class UpdateManager
$http->toFile($filePath);
});
if ($result->code != 200) {
throw new ApplicationException(File::get($filePath));
if (in_array($result->code, [301, 302])) {
if ($redirectUrl = array_get($result->info, 'redirect_url')) {
$result = Http::get($redirectUrl, function ($http) use ($postData, $filePath) {
$http->toFile($filePath);
});
}
}
if (md5_file($filePath) != $expectedHash) {
@unlink($filePath);
throw new ApplicationException(Lang::get('system::lang.server.file_corrupt'));
if ($result->code != 200) {
throw new ApplicationException(File::get($filePath));
}
}
@ -942,7 +947,7 @@ class UpdateManager
*/
protected function createServerUrl($uri)
{
$gateway = Config::get('cms.updateServer', 'http://gateway.octobercms.com/api');
$gateway = Config::get('cms.updateServer', 'https://gateway.octobercms.com/api');
if (substr($gateway, -1) != '/') {
$gateway .= '/';
}
@ -958,10 +963,10 @@ class UpdateManager
*/
protected function applyHttpAttributes($http, $postData)
{
$postData['protocol_version'] = '1.1';
$postData['protocol_version'] = '1.2';
$postData['client'] = 'october';
$postData['server'] = base64_encode(serialize([
$postData['server'] = base64_encode(json_encode([
'php' => PHP_VERSION,
'url' => Url::to('/'),
'since' => PluginVersion::orderBy('created_at')->value('created_at')

View File

@ -126,6 +126,8 @@ class VersionManager
*/
protected function applyPluginUpdate($code, $version, $details)
{
$version = $this->normalizeVersion($version);
list($comments, $scripts) = $this->extractScriptsAndComments($details);
/*
@ -291,13 +293,18 @@ class VersionManager
$versionInfo = [];
}
if ($versionInfo) {
uksort($versionInfo, function ($a, $b) {
return version_compare($a, $b);
});
// Sort result
uksort($versionInfo, function ($a, $b) {
return version_compare($a, $b);
});
$result = [];
foreach ($versionInfo as $version => $info) {
$result[$this->normalizeVersion($version)] = $info;
}
return $this->fileVersions[$code] = $versionInfo;
return $this->fileVersions[$code] = $result;
}
/**
@ -549,6 +556,11 @@ class VersionManager
return $this;
}
protected function normalizeVersion($version)
{
return ltrim((string) $version, 'v');
}
/**
* @param $details
*

View File

@ -18,10 +18,18 @@ final class SecurityPolicy implements SecurityPolicyInterface
* @var array List of forbidden methods.
*/
protected $blockedMethods = [
// \October\Rain\Extension\ExtendableTrait
'addDynamicMethod',
'addDynamicProperty',
// \October\Rain\Support\Traits\Emitter
'bindEvent',
'bindEventOnce',
// Eloquent & Halcyon data modification
'insert',
'update',
'delete',
];
/**

View File

@ -251,4 +251,5 @@
'AhmadFatoni\\ApiGenerator\\Controllers\\ApiGeneratorController' => 'plugins/ahmadfatoni/apigenerator/controllers/ApiGeneratorController.php',
'Zen\\Robots\\Controllers\\Generate' => 'plugins/zen/robots/controllers/Generate.php',
'Tps\\Reklama\\Widgets\\Stats' => 'plugins/tps/reklama/widgets/Stats.php',
'indikator\\devtools\\Plugin' => 'plugins/indikator/devtools/Plugin.php',
);

View File

@ -72,5 +72,29 @@ slug = "{{ :slug }}"
update: { view: '@#view' },
})
var re = /\[video poster=\"(.+?)\".+?mp4=\"(.+?)\"/g;
// re = /(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?/g
var ptags = document.querySelectorAll('p');
ptags.forEach(s =>{
var m;
m = re.exec(s.innerText);
if (m) {
var video =document.createElement('video');
video.setAttribute('src',m[2]);
video.setAttribute('controls',"")
video.setAttribute('class',"aligncenter")
video.setAttribute('type',"video/mp4")
video.setAttribute('poster',m[1])
s.parentNode.replaceChild(video,s);
console.log(m);
console.log(m[1]);
}
else{
}
});
</script>
{% endput %}
{% endput %}

View File

@ -6,14 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'ArithmeticError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php',
'AssertionError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php',
'DivisionByZeroError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php',
'Error' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/Error.php',
'JSMin' => $vendorDir . '/linkorb/jsmin-php/src/jsmin-1.1.1.php',
'JSMinException' => $vendorDir . '/linkorb/jsmin-php/src/jsmin-1.1.1.php',
'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'ParseError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ParseError.php',
'SessionUpdateTimestampHandlerInterface' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php',
'TypeError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/TypeError.php',
);

View File

@ -8,9 +8,7 @@ $baseDir = dirname($vendorDir);
return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'023d27dca8066ef29e6739335ea73bad' => $vendorDir . '/symfony/polyfill-php70/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'bd9634f2d41831496de0d3dfe4c94881' => $vendorDir . '/symfony/polyfill-php56/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',

View File

@ -12,6 +12,5 @@ return array(
'Parsedown' => array($vendorDir . '/erusev/parsedown'),
'Less' => array($vendorDir . '/october/rain/src/Parse/Assetic/Less/lib'),
'Doctrine\\DBAL\\' => array($vendorDir . '/doctrine/dbal/lib'),
'Doctrine\\Common\\Collections\\' => array($vendorDir . '/doctrine/collections/lib'),
'Assetic' => array($vendorDir . '/kriswallsmith/assetic/src'),
);

View File

@ -7,19 +7,17 @@ $baseDir = dirname($vendorDir);
return array(
'XdgBaseDir\\' => array($vendorDir . '/dnoegel/php-xdg-base-dir/src'),
'Wikimedia\\Composer\\' => array($vendorDir . '/wikimedia/composer-merge-plugin/src'),
'Twig\\' => array($vendorDir . '/twig/twig/src'),
'TijsVerkoyen\\CssToInlineStyles\\' => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'),
'System\\' => array($baseDir . '/modules/system'),
'Symfony\\Polyfill\\Util\\' => array($vendorDir . '/symfony/polyfill-util'),
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),
'Symfony\\Polyfill\\Php70\\' => array($vendorDir . '/symfony/polyfill-php70'),
'Symfony\\Polyfill\\Php56\\' => array($vendorDir . '/symfony/polyfill-php56'),
'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\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'),
'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'),
'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
@ -39,8 +37,8 @@ return array(
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
'October\\Rain\\' => array($vendorDir . '/october/rain/src'),
'October\\Demo\\' => array($baseDir . '/plugins/october/demo'),
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'),
'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'),
'League\\Csv\\' => array($vendorDir . '/league/csv/src'),
'Leafo\\ScssPhp\\' => array($vendorDir . '/leafo/scssphp/src'),
@ -51,8 +49,10 @@ return array(
'Illuminate\\' => array($vendorDir . '/laravel/framework/src/Illuminate'),
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'),
'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'),
'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Inflector'),
'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'),
'Doctrine\\Common\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Common/Inflector'),
'Doctrine\\Common\\Collections\\' => array($vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections'),
'Doctrine\\Common\\Cache\\' => array($vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache'),
'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'),
'Doctrine\\Common\\' => array($vendorDir . '/doctrine/common/lib/Doctrine/Common'),

View File

@ -9,9 +9,7 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
public static $files = array (
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
'bd9634f2d41831496de0d3dfe4c94881' => __DIR__ . '/..' . '/symfony/polyfill-php56/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
@ -32,10 +30,6 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
'XdgBaseDir\\' => 11,
),
'W' =>
array (
'Wikimedia\\Composer\\' => 19,
),
'T' =>
array (
'Twig\\' => 5,
@ -44,15 +38,14 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
'S' =>
array (
'System\\' => 7,
'Symfony\\Polyfill\\Util\\' => 22,
'Symfony\\Polyfill\\Php72\\' => 23,
'Symfony\\Polyfill\\Php70\\' => 23,
'Symfony\\Polyfill\\Php56\\' => 23,
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
'Symfony\\Polyfill\\Intl\\Idn\\' => 26,
'Symfony\\Polyfill\\Iconv\\' => 23,
'Symfony\\Polyfill\\Ctype\\' => 23,
'Symfony\\Contracts\\Translation\\' => 30,
'Symfony\\Contracts\\EventDispatcher\\' => 34,
'Symfony\\Component\\Yaml\\' => 23,
'Symfony\\Component\\VarDumper\\' => 28,
'Symfony\\Component\\Translation\\' => 30,
@ -81,7 +74,6 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
'O' =>
array (
'October\\Rain\\' => 13,
'October\\Demo\\' => 13,
),
'M' =>
array (
@ -89,6 +81,7 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
),
'L' =>
array (
'League\\MimeTypeDetection\\' => 25,
'League\\Flysystem\\' => 17,
'League\\Csv\\' => 11,
'Leafo\\ScssPhp\\' => 14,
@ -111,8 +104,10 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
'D' =>
array (
'Dotenv\\' => 7,
'Doctrine\\Inflector\\' => 19,
'Doctrine\\Common\\Lexer\\' => 22,
'Doctrine\\Common\\Inflector\\' => 26,
'Doctrine\\Common\\Collections\\' => 28,
'Doctrine\\Common\\Cache\\' => 22,
'Doctrine\\Common\\Annotations\\' => 28,
'Doctrine\\Common\\' => 16,
@ -134,10 +129,6 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
0 => __DIR__ . '/..' . '/dnoegel/php-xdg-base-dir/src',
),
'Wikimedia\\Composer\\' =>
array (
0 => __DIR__ . '/..' . '/wikimedia/composer-merge-plugin/src',
),
'Twig\\' =>
array (
0 => __DIR__ . '/..' . '/twig/twig/src',
@ -150,22 +141,10 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
0 => __DIR__ . '/../..' . '/modules/system',
),
'Symfony\\Polyfill\\Util\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-util',
),
'Symfony\\Polyfill\\Php72\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php72',
),
'Symfony\\Polyfill\\Php70\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php70',
),
'Symfony\\Polyfill\\Php56\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php56',
),
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
@ -186,6 +165,14 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
),
'Symfony\\Contracts\\Translation\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/translation-contracts',
),
'Symfony\\Contracts\\EventDispatcher\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts',
),
'Symfony\\Component\\Yaml\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/yaml',
@ -262,14 +249,14 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
0 => __DIR__ . '/..' . '/october/rain/src',
),
'October\\Demo\\' =>
array (
0 => __DIR__ . '/../..' . '/plugins/october/demo',
),
'Monolog\\' =>
array (
0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
),
'League\\MimeTypeDetection\\' =>
array (
0 => __DIR__ . '/..' . '/league/mime-type-detection/src',
),
'League\\Flysystem\\' =>
array (
0 => __DIR__ . '/..' . '/league/flysystem/src',
@ -310,6 +297,10 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
0 => __DIR__ . '/..' . '/vlucas/phpdotenv/src',
),
'Doctrine\\Inflector\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/inflector/lib/Doctrine/Inflector',
),
'Doctrine\\Common\\Lexer\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer',
@ -318,6 +309,10 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
0 => __DIR__ . '/..' . '/doctrine/inflector/lib/Doctrine/Common/Inflector',
),
'Doctrine\\Common\\Collections\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/collections/lib/Doctrine/Common/Collections',
),
'Doctrine\\Common\\Cache\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache',
@ -391,10 +386,6 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
array (
0 => __DIR__ . '/..' . '/doctrine/dbal/lib',
),
'Doctrine\\Common\\Collections\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/collections/lib',
),
),
'A' =>
array (
@ -406,16 +397,9 @@ class ComposerStaticInitce290a037d2cbd6fc6b8d537449d0ac2
);
public static $classMap = array (
'ArithmeticError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php',
'AssertionError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php',
'DivisionByZeroError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php',
'Error' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/Error.php',
'JSMin' => __DIR__ . '/..' . '/linkorb/jsmin-php/src/jsmin-1.1.1.php',
'JSMinException' => __DIR__ . '/..' . '/linkorb/jsmin-php/src/jsmin-1.1.1.php',
'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'ParseError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ParseError.php',
'SessionUpdateTimestampHandlerInterface' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php',
'TypeError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/TypeError.php',
);
public static function getInitializer(ClassLoader $loader)

File diff suppressed because it is too large Load Diff

View File

@ -57,12 +57,14 @@
"PPI",
"Puppet",
"Porto",
"ProcessWire",
"RadPHP",
"ReIndex",
"Roundcube",
"shopware",
"SilverStripe",
"SMF",
"Starbug",
"SyDES",
"Sylius",
"symfony",
@ -86,10 +88,13 @@
"autoload": {
"psr-4": { "Composer\\Installers\\": "src/Composer/Installers" }
},
"autoload-dev": {
"psr-4": { "Composer\\Installers\\Test\\": "tests/Composer/Installers/Test" }
},
"extra": {
"class": "Composer\\Installers\\Plugin",
"branch-alias": {
"dev-master": "1.0-dev"
"dev-main": "1.x-dev"
}
},
"replace": {
@ -100,13 +105,15 @@
"composer-plugin-api": "^1.0 || ^2.0"
},
"require-dev": {
"composer/composer": "1.6.* || 2.0.*@dev",
"composer/semver": "1.0.* || 2.0.*@dev",
"phpunit/phpunit": "^4.8.36",
"sebastian/comparator": "^1.2.4",
"symfony/process": "^2.3"
"composer/composer": "1.6.* || ^2.0",
"composer/semver": "^1 || ^3",
"symfony/phpunit-bridge": "^4.2 || ^5",
"phpstan/phpstan": "^0.12.55",
"symfony/process": "^2.3",
"phpstan/phpstan-phpunit": "^0.12.16"
},
"scripts": {
"test": "phpunit"
"test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor/bin/simple-phpunit",
"phpstan": "vendor/bin/phpstan analyse"
}
}

View File

@ -74,8 +74,8 @@ abstract class BaseInstaller
/**
* For an installer to override to modify the vars per installer.
*
* @param array $vars
* @return array
* @param array<string, string> $vars This will normally receive array{name: string, vendor: string, type: string}
* @return array<string, string>
*/
public function inflectPackageVars($vars)
{
@ -85,7 +85,7 @@ abstract class BaseInstaller
/**
* Gets the installer's locations
*
* @return array
* @return array<string, string> map of package types => install path
*/
public function getLocations()
{
@ -95,8 +95,8 @@ abstract class BaseInstaller
/**
* Replace vars in a path
*
* @param string $path
* @param array $vars
* @param string $path
* @param array<string, string> $vars
* @return string
*/
protected function templatePath($path, array $vars = array())
@ -121,7 +121,7 @@ abstract class BaseInstaller
* @param string $name
* @param string $type
* @param string $vendor = NULL
* @return string
* @return string|false
*/
protected function mapCustomInstallPaths(array $paths, $name, $type, $vendor = NULL)
{

View File

@ -2,6 +2,7 @@
namespace Composer\Installers;
use Composer\DependencyResolver\Pool;
use Composer\Semver\Constraint\Constraint;
class CakePHPInstaller extends BaseInstaller
{
@ -49,14 +50,6 @@ class CakePHPInstaller extends BaseInstaller
*/
protected function matchesCakeVersion($matcher, $version)
{
if (class_exists('Composer\Semver\Constraint\MultiConstraint')) {
$multiClass = 'Composer\Semver\Constraint\MultiConstraint';
$constraintClass = 'Composer\Semver\Constraint\Constraint';
} else {
$multiClass = 'Composer\Package\LinkConstraint\MultiConstraint';
$constraintClass = 'Composer\Package\LinkConstraint\VersionConstraint';
}
$repositoryManager = $this->composer->getRepositoryManager();
if (! $repositoryManager) {
return false;
@ -67,6 +60,6 @@ class CakePHPInstaller extends BaseInstaller
return false;
}
return $repos->findPackage('cakephp/cakephp', new $constraintClass($matcher, $version)) !== null;
return $repos->findPackage('cakephp/cakephp', new Constraint($matcher, $version)) !== null;
}
}

View File

@ -12,9 +12,7 @@ class CockpitInstaller extends BaseInstaller
*
* Strip `module-` prefix from package name.
*
* @param array @vars
*
* @return array
* {@inheritDoc}
*/
public function inflectPackageVars($vars)
{

View File

@ -9,6 +9,7 @@ use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\Filesystem;
use React\Promise\PromiseInterface;
class Installer extends LibraryInstaller
{
@ -87,6 +88,7 @@ class Installer extends LibraryInstaller
'radphp' => 'RadPHPInstaller',
'phifty' => 'PhiftyInstaller',
'porto' => 'PortoInstaller',
'processwire' => 'ProcessWireInstaller',
'redaxo' => 'RedaxoInstaller',
'redaxo5' => 'Redaxo5Installer',
'reindex' => 'ReIndexInstaller',
@ -95,6 +97,7 @@ class Installer extends LibraryInstaller
'sitedirect' => 'SiteDirectInstaller',
'silverstripe' => 'SilverStripeInstaller',
'smf' => 'SMFInstaller',
'starbug' => 'StarbugInstaller',
'sydes' => 'SyDESInstaller',
'sylius' => 'SyliusInstaller',
'symfony1' => 'Symfony1Installer',
@ -160,9 +163,23 @@ class Installer extends LibraryInstaller
public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
{
parent::uninstall($repo, $package);
$installPath = $this->getPackageBasePath($package);
$this->io->write(sprintf('Deleting %s - %s', $installPath, !file_exists($installPath) ? '<comment>deleted</comment>' : '<error>not deleted</error>'));
$io = $this->io;
$outputStatus = function () use ($io, $installPath) {
$io->write(sprintf('Deleting %s - %s', $installPath, !file_exists($installPath) ? '<comment>deleted</comment>' : '<error>not deleted</error>'));
};
$promise = parent::uninstall($repo, $package);
// Composer v2 might return a promise here
if ($promise instanceof PromiseInterface) {
return $promise->then($outputStatus);
}
// If not, execute the code right away as parent::uninstall executed synchronously (composer v1, or v2 without async)
$outputStatus();
return null;
}
/**
@ -184,23 +201,20 @@ class Installer extends LibraryInstaller
/**
* Finds a supported framework type if it exists and returns it
*
* @param string $type
* @return string
* @param string $type
* @return string|false
*/
protected function findFrameworkType($type)
{
$frameworkType = false;
krsort($this->supportedTypes);
foreach ($this->supportedTypes as $key => $val) {
if ($key === substr($type, 0, strlen($key))) {
$frameworkType = substr($type, 0, strlen($key));
break;
return substr($type, 0, strlen($key));
}
}
return $frameworkType;
return false;
}
/**

View File

@ -18,6 +18,7 @@ class MoodleInstaller extends BaseInstaller
'cachestore' => 'cache/stores/{$name}/',
'cachelock' => 'cache/locks/{$name}/',
'calendartype' => 'calendar/type/{$name}/',
'fileconverter' => 'files/converter/{$name}/',
'format' => 'course/format/{$name}/',
'coursereport' => 'course/report/{$name}/',
'customcertelement' => 'mod/customcert/element/{$name}/',

View File

@ -18,7 +18,7 @@ class OxidInstaller extends BaseInstaller
*
* @param PackageInterface $package
* @param string $frameworkType
* @return void
* @return string
*/
public function getInstallPath(PackageInterface $package, $frameworkType = '')
{

View File

@ -13,9 +13,7 @@ class SyDESInstaller extends BaseInstaller
*
* Strip `sydes-` prefix and a trailing '-theme' or '-module' from package name if present.
*
* @param array @vars
*
* @return array
* {@inerhitDoc}
*/
public function inflectPackageVars($vars)
{

View File

@ -6,7 +6,25 @@ namespace Composer\Installers;
*/
class TaoInstaller extends BaseInstaller
{
const EXTRA_TAO_EXTENSION_NAME = 'tao-extension-name';
protected $locations = array(
'extension' => '{$name}'
);
public function inflectPackageVars($vars)
{
$extra = $this->package->getExtra();
if (array_key_exists(self::EXTRA_TAO_EXTENSION_NAME, $extra)) {
$vars['name'] = $extra[self::EXTRA_TAO_EXTENSION_NAME];
return $vars;
}
$vars['name'] = str_replace('extension-', '', $vars['name']);
$vars['name'] = str_replace('-', ' ', $vars['name']);
$vars['name'] = lcfirst(str_replace(' ', '', ucwords($vars['name'])));
return $vars;
}
}

View File

@ -1,5 +1,52 @@
## 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&#95;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

View File

@ -1,16 +1,21 @@
# Doctrine Annotations
[![Build Status](https://travis-ci.org/doctrine/annotations.svg?branch=master)](https://travis-ci.org/doctrine/annotations)
[![Build Status](https://github.com/doctrine/annotations/workflows/Continuous%20Integration/badge.svg?label=build)](https://github.com/doctrine/persistence/actions)
[![Dependency Status](https://www.versioneye.com/package/php--doctrine--annotations/badge.png)](https://www.versioneye.com/package/php--doctrine--annotations)
[![Reference Status](https://www.versioneye.com/php/doctrine:annotations/reference_badge.svg)](https://www.versioneye.com/php/doctrine:annotations/references)
[![Total Downloads](https://poser.pugx.org/doctrine/annotations/downloads.png)](https://packagist.org/packages/doctrine/annotations)
[![Latest Stable Version](https://poser.pugx.org/doctrine/annotations/v/stable.png)](https://packagist.org/packages/doctrine/annotations)
[![Latest Stable Version](https://img.shields.io/packagist/v/doctrine/annotations.svg?label=stable)](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](http://docs.doctrine-project.org/projects/doctrine-common/en/latest/reference/annotations.html).
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

View File

@ -3,7 +3,7 @@
"type": "library",
"description": "Docblock Annotations Parser",
"keywords": ["annotations", "docblock", "parser"],
"homepage": "http://www.doctrine-project.org",
"homepage": "https://www.doctrine-project.org/projects/annotations.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
@ -13,22 +13,30 @@
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
],
"require": {
"php": "^5.6 || ^7.0",
"php": "^7.1 || ^8.0",
"ext-tokenizer": "*",
"doctrine/lexer": "1.*"
},
"require-dev": {
"doctrine/cache": "1.*",
"phpunit/phpunit": "^5.7"
"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\\Tests\\Common\\Annotations\\": "tests/Doctrine/Tests/Common/Annotations" }
},
"extra": {
"branch-alias": {
"dev-master": "1.4.x-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"
]
}
}

View File

@ -1,47 +1,27 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations;
use BadMethodCallException;
use function sprintf;
/**
* Annotations class.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class Annotation
{
/**
* Value property. Common among all derived classes.
*
* @var string
* @var mixed
*/
public $value;
/**
* Constructor.
*
* @param array $data Key-value for properties to be defined in this class.
* @param array<string, mixed> $data Key-value for properties to be defined in this class.
*/
public final function __construct(array $data)
final public function __construct(array $data)
{
foreach ($data as $key => $value) {
$this->$key = $value;
@ -53,12 +33,12 @@ class Annotation
*
* @param string $name Unknown property name.
*
* @throws \BadMethodCallException
* @throws BadMethodCallException
*/
public function __get($name)
{
throw new \BadMethodCallException(
sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this))
throw new BadMethodCallException(
sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class)
);
}
@ -68,12 +48,12 @@ class Annotation
* @param string $name Unknown property name.
* @param mixed $value Property value.
*
* @throws \BadMethodCallException
* @throws BadMethodCallException
*/
public function __set($name, $value)
{
throw new \BadMethodCallException(
sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this))
throw new BadMethodCallException(
sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class)
);
}
}

View File

@ -1,47 +1,21 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that can be used to signal to the parser
* to check the attribute type during the parsing process.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*
* @Annotation
*/
final class Attribute
{
/**
* @var string
*/
/** @var string */
public $name;
/**
* @var string
*/
/** @var string */
public $type;
/**
* @var boolean
*/
/** @var bool */
public $required = false;
}

View File

@ -1,37 +1,15 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*
* @Annotation
*/
final class Attributes
{
/**
* @var array<Doctrine\Common\Annotations\Annotation\Attribute>
*/
/** @var array<Attribute> */
public $value;
}

View File

@ -1,32 +1,20 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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.
*
* @since 2.4
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*
* @Annotation
* @Attributes({
* @Attribute("value", required = true, type = "array"),
@ -35,34 +23,30 @@ namespace Doctrine\Common\Annotations\Annotation;
*/
final class Enum
{
/**
* @var array
*/
/** @phpstan-var list<scalar> */
public $value;
/**
* Literal target declaration.
*
* @var array
* @var mixed[]
*/
public $literal;
/**
* Annotation constructor.
* @throws InvalidArgumentException
*
* @param array $values
*
* @throws \InvalidArgumentException
* @phpstan-param array{literal?: mixed[], value: list<scalar>} $values
*/
public function __construct(array $values)
{
if ( ! isset($values['literal'])) {
$values['literal'] = array();
if (! isset($values['literal'])) {
$values['literal'] = [];
}
foreach ($values['value'] as $var) {
if( ! is_scalar($var)) {
throw new \InvalidArgumentException(sprintf(
if (! is_scalar($var)) {
throw new InvalidArgumentException(sprintf(
'@Enum supports only scalar values "%s" given.',
is_object($var) ? get_class($var) : gettype($var)
));
@ -70,15 +54,16 @@ final class Enum
}
foreach ($values['literal'] as $key => $var) {
if( ! in_array($key, $values['value'])) {
throw new \InvalidArgumentException(sprintf(
if (! in_array($key, $values['value'])) {
throw new InvalidArgumentException(sprintf(
'Undefined enumerator value "%s" for literal "%s".',
$key , $var
$key,
$var
));
}
}
$this->value = $values['value'];
$this->literal = $values['literal'];
$this->value = $values['value'];
$this->literal = $values['literal'];
}
}

View File

@ -1,52 +1,41 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
final class IgnoreAnnotation
{
/**
* @var array
*/
/** @phpstan-var list<string> */
public $names;
/**
* Constructor.
* @throws RuntimeException
*
* @param array $values
*
* @throws \RuntimeException
* @phpstan-param array{value: string|list<string>} $values
*/
public function __construct(array $values)
{
if (is_string($values['value'])) {
$values['value'] = array($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'])));
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'];

View File

@ -1,31 +1,11 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*
* @Annotation
*/
final class Required

View File

@ -1,89 +1,79 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*
* @Annotation
*/
final class Target
{
const TARGET_CLASS = 1;
const TARGET_METHOD = 2;
const TARGET_PROPERTY = 4;
const TARGET_ANNOTATION = 8;
const TARGET_ALL = 15;
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
*/
private static $map = array(
/** @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,
);
];
/**
* @var array
*/
/** @phpstan-var list<string> */
public $value;
/**
* Targets as bitmask.
*
* @var integer
* @var int
*/
public $targets;
/**
* Literal target declaration.
*
* @var integer
* @var string
*/
public $literal;
/**
* Annotation constructor.
* @throws InvalidArgumentException
*
* @param array $values
*
* @throws \InvalidArgumentException
* @phpstan-param array{value?: string|list<string>} $values
*/
public function __construct(array $values)
{
if (!isset($values['value'])){
if (! isset($values['value'])) {
$values['value'] = null;
}
if (is_string($values['value'])){
$values['value'] = array($values['value']);
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.',
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'])
)
);
@ -91,17 +81,21 @@ final class Target
$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)))
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);
$this->targets = $bitmask;
$this->value = $values['value'];
$this->literal = implode(', ', $this->value);
}
}

View File

@ -1,34 +1,19 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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
*
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class AnnotationException extends \Exception
class AnnotationException extends Exception
{
/**
* Creates a new AnnotationException describing a Syntax error.
@ -58,8 +43,6 @@ class AnnotationException extends \Exception
* Creates a new AnnotationException describing an error which occurred during
* the creation of the annotation.
*
* @since 2.2
*
* @param string $message
*
* @return AnnotationException
@ -72,8 +55,6 @@ class AnnotationException extends \Exception
/**
* Creates a new AnnotationException describing a type error.
*
* @since 1.1
*
* @param string $message
*
* @return AnnotationException
@ -86,8 +67,6 @@ class AnnotationException extends \Exception
/**
* Creates a new AnnotationException describing a constant semantical error.
*
* @since 2.3
*
* @param string $identifier
* @param string $context
*
@ -105,8 +84,6 @@ class AnnotationException extends \Exception
/**
* Creates a new AnnotationException describing an type error of an attribute.
*
* @since 2.2
*
* @param string $attributeName
* @param string $annotationName
* @param string $context
@ -130,8 +107,6 @@ class AnnotationException extends \Exception
/**
* Creates a new AnnotationException describing an required error of an attribute.
*
* @since 2.2
*
* @param string $attributeName
* @param string $annotationName
* @param string $context
@ -153,21 +128,20 @@ class AnnotationException extends \Exception
/**
* Creates a new AnnotationException describing a invalid enummerator.
*
* @since 2.4
*
* @param string $attributeName
* @param string $annotationName
* @param string $context
* @param array $available
* @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 accept only [%s], but got %s.',
$attributeName,
'[Enum Error] Attribute "%s" of @%s declared on %s accepts only [%s], but got %s.',
$attributeName,
$annotationName,
$context,
implode(', ', $available),
@ -181,7 +155,7 @@ class AnnotationException extends \Exception
public static function optimizerPlusSaveComments()
{
return new self(
"You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1."
'You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1.'
);
}
@ -191,7 +165,7 @@ class AnnotationException extends \Exception
public static function optimizerPlusLoadComments()
{
return new self(
"You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1."
'You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1.'
);
}
}

View File

@ -1,122 +1,57 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class AnnotationReader implements Reader
{
/**
* Global map for imports.
*
* @var array
* @var array<string, class-string>
*/
private static $globalImports = array(
'ignoreannotation' => 'Doctrine\Common\Annotations\Annotation\IgnoreAnnotation',
);
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
* @var array<string, true>
*/
private static $globalIgnoredNames = array(
// Annotation tags
'Annotation' => true, 'Attribute' => true, 'Attributes' => true,
/* Can we enable this? 'Enum' => true, */
'Required' => true,
'Target' => true,
// Widely used tags (but not existent in phpdoc)
'fix' => true , 'fixme' => true,
'override' => true,
// PHPDocumentor 1 tags
'abstract'=> true, 'access'=> true,
'code' => true,
'deprec'=> true,
'endcode' => true, 'exception'=> true,
'final'=> true,
'ingroup' => true, 'inheritdoc'=> true, 'inheritDoc'=> true,
'magic' => true,
'name'=> true,
'toc' => true, 'tutorial'=> true,
'private' => true,
'static'=> true, 'staticvar'=> true, 'staticVar'=> true,
'throw' => true,
// PHPDocumentor 2 tags.
'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,
// PHPUnit tags
'codeCoverageIgnore' => true, 'codeCoverageIgnoreStart' => true, 'codeCoverageIgnoreEnd' => true,
// PHPCheckStyle
'SuppressWarnings' => true,
// PHPStorm
'noinspection' => true,
// PEAR
'package_version' => true,
// PlantUML
'startuml' => true, 'enduml' => 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
* @var array<string, true>
*/
private static $globalIgnoredNamespaces = array();
private static $globalIgnoredNamespaces = [];
/**
* Add a new annotation to the globally ignored annotation names with regard to exception handling.
*
* @param string $name
*/
static public function addGlobalIgnoredName($name)
public static function addGlobalIgnoredName($name)
{
self::$globalIgnoredNames[$name] = true;
}
@ -126,7 +61,7 @@ class AnnotationReader implements Reader
*
* @param string $namespace
*/
static public function addGlobalIgnoredNamespace($namespace)
public static function addGlobalIgnoredNamespace($namespace)
{
self::$globalIgnoredNamespaces[$namespace] = true;
}
@ -134,75 +69,68 @@ class AnnotationReader implements Reader
/**
* Annotations parser.
*
* @var \Doctrine\Common\Annotations\DocParser
* @var DocParser
*/
private $parser;
/**
* Annotations parser used to collect parsing metadata.
*
* @var \Doctrine\Common\Annotations\DocParser
* @var DocParser
*/
private $preParser;
/**
* PHP parser used to collect imports.
*
* @var \Doctrine\Common\Annotations\PhpParser
* @var PhpParser
*/
private $phpParser;
/**
* In-memory cache mechanism to store imported annotations per class.
*
* @var array
* @psalm-var array<'class'|'function', array<string, array<string, class-string>>>
*/
private $imports = array();
private $imports = [];
/**
* In-memory cache mechanism to store ignored annotations per class.
*
* @var array
* @psalm-var array<'class'|'function', array<string, array<string, true>>>
*/
private $ignoredAnnotationNames = array();
private $ignoredAnnotationNames = [];
/**
* Constructor.
*
* Initializes a new AnnotationReader.
*
* @param DocParser $parser
* @throws AnnotationException
*/
public function __construct(DocParser $parser = null)
public function __construct(?DocParser $parser = null)
{
if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === "0" || ini_get('opcache.save_comments') === "0")) {
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) {
if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') === 0) {
throw AnnotationException::optimizerPlusSaveComments();
}
if (PHP_VERSION_ID < 70000) {
if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.load_comments') === "0" || ini_get('opcache.load_comments') === "0")) {
throw AnnotationException::optimizerPlusLoadComments();
}
if (extension_loaded('Zend OPcache') && ini_get('opcache.load_comments') == 0) {
throw AnnotationException::optimizerPlusLoadComments();
}
}
AnnotationRegistry::registerFile(__DIR__ . '/Annotation/IgnoreAnnotation.php');
// Make sure that the IgnoreAnnotation annotation is loaded
class_exists(IgnoreAnnotation::class);
$this->parser = $parser ?: new DocParser();
$this->preParser = new DocParser;
$this->preParser = new DocParser();
$this->preParser->setImports(self::$globalImports);
$this->preParser->setIgnoreNotImportedAnnotations(true);
$this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames);
$this->phpParser = new PhpParser;
$this->phpParser = new PhpParser();
}
/**
@ -211,7 +139,7 @@ class AnnotationReader implements Reader
public function getClassAnnotations(ReflectionClass $class)
{
$this->parser->setTarget(Target::TARGET_CLASS);
$this->parser->setImports($this->getClassImports($class));
$this->parser->setImports($this->getImports($class));
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
@ -240,7 +168,7 @@ class AnnotationReader implements Reader
public function getPropertyAnnotations(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$context = 'property ' . $class->getName() . "::\$" . $property->getName();
$context = 'property ' . $class->getName() . '::$' . $property->getName();
$this->parser->setTarget(Target::TARGET_PROPERTY);
$this->parser->setImports($this->getPropertyImports($property));
@ -299,66 +227,103 @@ class AnnotationReader implements Reader
}
/**
* Returns the ignored annotations for the given class.
* Gets the annotations applied to a function.
*
* @param \ReflectionClass $class
*
* @return array
* @phpstan-return list<object> An array of Annotations.
*/
private function getIgnoredAnnotationNames(ReflectionClass $class)
public function getFunctionAnnotations(ReflectionFunction $function): array
{
$name = $class->getName();
if (isset($this->ignoredAnnotationNames[$name])) {
return $this->ignoredAnnotationNames[$name];
}
$context = 'function ' . $function->getName();
$this->collectParsingMetadata($class);
$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->ignoredAnnotationNames[$name];
return $this->parser->parse($function->getDocComment(), $context);
}
/**
* Retrieves imports.
* Gets a function annotation.
*
* @param \ReflectionClass $class
*
* @return array
* @return object|null The Annotation or NULL, if the requested annotation does not exist.
*/
private function getClassImports(ReflectionClass $class)
public function getFunctionAnnotation(ReflectionFunction $function, string $annotationName)
{
$name = $class->getName();
if (isset($this->imports[$name])) {
return $this->imports[$name];
$annotations = $this->getFunctionAnnotations($function);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
$this->collectParsingMetadata($class);
return null;
}
return $this->imports[$name];
/**
* 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.
*
* @param \ReflectionMethod $method
*
* @return array
* @return array<string, class-string>
*/
private function getMethodImports(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
$classImports = $this->getClassImports($class);
if (!method_exists($class, 'getTraits')) {
return $classImports;
}
$class = $method->getDeclaringClass();
$classImports = $this->getImports($class);
$traitImports = array();
$traitImports = [];
foreach ($class->getTraits() as $trait) {
if ($trait->hasMethod($method->getName())
&& $trait->getFileName() === $method->getFileName()
if (
! $trait->hasMethod($method->getName())
|| $trait->getFileName() !== $method->getFileName()
) {
$traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
continue;
}
$traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait));
}
return array_merge($classImports, $traitImports);
@ -367,55 +332,58 @@ class AnnotationReader implements Reader
/**
* Retrieves imports for properties.
*
* @param \ReflectionProperty $property
*
* @return array
* @return array<string, class-string>
*/
private function getPropertyImports(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$classImports = $this->getClassImports($class);
if (!method_exists($class, 'getTraits')) {
return $classImports;
}
$class = $property->getDeclaringClass();
$classImports = $this->getImports($class);
$traitImports = array();
$traitImports = [];
foreach ($class->getTraits() as $trait) {
if ($trait->hasProperty($property->getName())) {
$traitImports = array_merge($traitImports, $this->phpParser->parseClass($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.
* Collects parsing metadata for a given class or function.
*
* @param \ReflectionClass $class
* @param ReflectionClass|ReflectionFunction $reflection
*/
private function collectParsingMetadata(ReflectionClass $class)
private function collectParsingMetadata($reflection): void
{
$type = $reflection instanceof ReflectionClass ? 'class' : 'function';
$name = $reflection->getName();
$ignoredAnnotationNames = self::$globalIgnoredNames;
$annotations = $this->preParser->parse($class->getDocComment(), 'class ' . $class->name);
$annotations = $this->preParser->parse($reflection->getDocComment(), $type . ' ' . $name);
foreach ($annotations as $annotation) {
if ($annotation instanceof IgnoreAnnotation) {
foreach ($annotation->names AS $annot) {
$ignoredAnnotationNames[$annot] = true;
}
if (! ($annotation instanceof IgnoreAnnotation)) {
continue;
}
foreach ($annotation->names as $annot) {
$ignoredAnnotationNames[$annot] = true;
}
}
$name = $class->getName();
$this->imports[$name] = array_merge(
$this->imports[$type][$name] = array_merge(
self::$globalImports,
$this->phpParser->parseClass($class),
array('__NAMESPACE__' => $class->getNamespaceName())
$this->phpParser->parseUseStatements($reflection),
[
'__NAMESPACE__' => $reflection->getNamespaceName(),
'self' => $name,
]
);
$this->ignoredAnnotationNames[$name] = $ignoredAnnotationNames;
$this->ignoredAnnotationNames[$type][$name] = $ignoredAnnotationNames;
}
}

View File

@ -1,27 +1,18 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations;
/**
* AnnotationRegistry.
*/
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
{
/**
@ -32,35 +23,49 @@ final class AnnotationRegistry
*
* This autoloading mechanism does not utilize the PHP autoloading but implements autoloading on its own.
*
* @var array
* @var string[][]|string[]|null[]
*/
static private $autoloadNamespaces = array();
private static $autoloadNamespaces = [];
/**
* A map of autoloader callables.
*
* @var array
* @var callable[]
*/
static private $loaders = array();
private static $loaders = [];
/**
* @return void
* An array of classes which cannot be found
*
* @var null[] indexed by class name
*/
static public function reset()
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 = array();
self::$loaders = array();
self::$autoloadNamespaces = [];
self::$loaders = [];
self::$failedToAutoload = [];
self::$registerFileUsed = false;
}
/**
* Registers file.
*
* @param string $file
*
* @return void
* @deprecated This method is deprecated and will be removed in
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
*/
static public function registerFile($file)
public static function registerFile(string $file): void
{
self::$registerFileUsed = true;
require_once $file;
}
@ -69,12 +74,12 @@ final class AnnotationRegistry
*
* Loading of this namespaces will be done with a PSR-0 namespace loading algorithm.
*
* @param string $namespace
* @param string|array|null $dirs
* @deprecated This method is deprecated and will be removed in
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
*
* @return void
* @phpstan-param string|list<string>|null $dirs
*/
static public function registerAutoloadNamespace($namespace, $dirs = null)
public static function registerAutoloadNamespace(string $namespace, $dirs = null): void
{
self::$autoloadNamespaces[$namespace] = $dirs;
}
@ -84,11 +89,12 @@ final class AnnotationRegistry
*
* Loading of this namespaces will be done with a PSR-0 namespace loading algorithm.
*
* @param array $namespaces
* @deprecated This method is deprecated and will be removed in
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
*
* @return void
* @param string[][]|string[]|null[] $namespaces indexed by namespace name
*/
static public function registerAutoloadNamespaces(array $namespaces)
public static function registerAutoloadNamespaces(array $namespaces): void
{
self::$autoloadNamespaces = array_merge(self::$autoloadNamespaces, $namespaces);
}
@ -99,53 +105,86 @@ final class AnnotationRegistry
* 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.
*
* @param callable $callable
*
* @return void
*
* @throws \InvalidArgumentException
* @deprecated This method is deprecated and will be removed in
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
*/
static public function registerLoader($callable)
public static function registerLoader(callable $callable): void
{
if (!is_callable($callable)) {
throw new \InvalidArgumentException("A callable is expected in AnnotationRegistry::registerLoader().");
// 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::$loaders[] = $callable;
self::registerLoader($callable);
}
/**
* Autoloads an annotation class silently.
*
* @param string $class
*
* @return boolean
*/
static public function loadAnnotationClass($class)
public static function loadAnnotationClass(string $class): bool
{
foreach (self::$autoloadNamespaces AS $namespace => $dirs) {
if (strpos($class, $namespace) === 0) {
$file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php";
if ($dirs === null) {
if ($path = stream_resolve_include_path($file)) {
require $path;
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;
}
} 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 (call_user_func($loader, $class) === 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;
}
}

View File

@ -1,67 +1,47 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
final class CachedReader implements Reader
{
/**
* @var Reader
*/
/** @var Reader */
private $delegate;
/**
* @var Cache
*/
/** @var Cache */
private $cache;
/**
* @var boolean
*/
/** @var bool */
private $debug;
/**
* @var array
*/
private $loadedAnnotations = array();
/** @var array<string, array<object>> */
private $loadedAnnotations = [];
/** @var int[] */
private $loadedFilemtimes = [];
/**
* Constructor.
*
* @param Reader $reader
* @param Cache $cache
* @param bool $debug
* @param bool $debug
*/
public function __construct(Reader $reader, Cache $cache, $debug = false)
{
$this->delegate = $reader;
$this->cache = $cache;
$this->debug = (boolean) $debug;
$this->cache = $cache;
$this->debug = (bool) $debug;
}
/**
@ -75,7 +55,8 @@ final class CachedReader implements Reader
return $this->loadedAnnotations[$cacheKey];
}
if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) {
$annots = $this->fetchFromCache($cacheKey, $class);
if ($annots === false) {
$annots = $this->delegate->getClassAnnotations($class);
$this->saveToCache($cacheKey, $annots);
}
@ -100,16 +81,17 @@ final class CachedReader implements Reader
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(\ReflectionProperty $property)
public function getPropertyAnnotations(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$cacheKey = $class->getName().'$'.$property->getName();
$class = $property->getDeclaringClass();
$cacheKey = $class->getName() . '$' . $property->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) {
$annots = $this->fetchFromCache($cacheKey, $class);
if ($annots === false) {
$annots = $this->delegate->getPropertyAnnotations($property);
$this->saveToCache($cacheKey, $annots);
}
@ -120,7 +102,7 @@ final class CachedReader implements Reader
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName)
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
foreach ($this->getPropertyAnnotations($property) as $annot) {
if ($annot instanceof $annotationName) {
@ -134,16 +116,17 @@ final class CachedReader implements Reader
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(\ReflectionMethod $method)
public function getMethodAnnotations(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
$cacheKey = $class->getName().'#'.$method->getName();
$class = $method->getDeclaringClass();
$cacheKey = $class->getName() . '#' . $method->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) {
$annots = $this->fetchFromCache($cacheKey, $class);
if ($annots === false) {
$annots = $this->delegate->getMethodAnnotations($method);
$this->saveToCache($cacheKey, $annots);
}
@ -154,7 +137,7 @@ final class CachedReader implements Reader
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(\ReflectionMethod $method, $annotationName)
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
foreach ($this->getMethodAnnotations($method) as $annot) {
if ($annot instanceof $annotationName) {
@ -172,21 +155,22 @@ final class CachedReader implements Reader
*/
public function clearLoadedAnnotations()
{
$this->loadedAnnotations = array();
$this->loadedAnnotations = [];
$this->loadedFilemtimes = [];
}
/**
* Fetches a value from the cache.
*
* @param string $cacheKey The cache key.
* @param ReflectionClass $class The related class.
* @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)
{
if (($data = $this->cache->fetch($cacheKey)) !== false) {
if (!$this->debug || $this->isCacheFresh($cacheKey, $class)) {
$data = $this->cache->fetch($cacheKey);
if ($data !== false) {
if (! $this->debug || $this->isCacheFresh($cacheKey, $class)) {
return $data;
}
}
@ -205,58 +189,76 @@ final class CachedReader implements Reader
private function saveToCache($cacheKey, $value)
{
$this->cache->save($cacheKey, $value);
if ($this->debug) {
$this->cache->save('[C]'.$cacheKey, time());
if (! $this->debug) {
return;
}
$this->cache->save('[C]' . $cacheKey, time());
}
/**
* Checks if the cache is fresh.
*
* @param string $cacheKey
* @param ReflectionClass $class
* @param string $cacheKey
*
* @return boolean
* @return bool
*/
private function isCacheFresh($cacheKey, ReflectionClass $class)
{
if (null === $lastModification = $this->getLastModification($class)) {
$lastModification = $this->getLastModification($class);
if ($lastModification === 0) {
return true;
}
return $this->cache->fetch('[C]'.$cacheKey) >= $lastModification;
return $this->cache->fetch('[C]' . $cacheKey) >= $lastModification;
}
/**
* Returns the time the class was last modified, testing traits and parents
*
* @param ReflectionClass $class
* @return int
*/
private function getLastModification(ReflectionClass $class)
private function getLastModification(ReflectionClass $class): int
{
$filename = $class->getFileName();
$parent = $class->getParentClass();
return max(array_merge(
if (isset($this->loadedFilemtimes[$filename])) {
return $this->loadedFilemtimes[$filename];
}
$parent = $class->getParentClass();
$lastModification = max(array_merge(
[$filename ? filemtime($filename) : 0],
array_map([$this, 'getTraitLastModificationTime'], $class->getTraits()),
array_map([$this, 'getLastModification'], $class->getInterfaces()),
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;
}
/**
* @param ReflectionClass $reflectionTrait
* @return int
*/
private function getTraitLastModificationTime(ReflectionClass $reflectionTrait)
private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int
{
$fileName = $reflectionTrait->getFileName();
return max(array_merge(
if (isset($this->loadedFilemtimes[$fileName])) {
return $this->loadedFilemtimes[$fileName];
}
$lastModificationTime = max(array_merge(
[$fileName ? filemtime($fileName) : 0],
array_map([$this, 'getTraitLastModificationTime'], $reflectionTrait->getTraits())
array_map(function (ReflectionClass $reflectionTrait): int {
return $this->getTraitLastModificationTime($reflectionTrait);
}, $reflectionTrait->getTraits())
));
assert($lastModificationTime !== false);
return $this->loadedFilemtimes[$fileName] = $lastModificationTime;
}
}

View File

@ -1,61 +1,46 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
final class DocLexer extends AbstractLexer
{
const T_NONE = 1;
const T_INTEGER = 2;
const T_STRING = 3;
const T_FLOAT = 4;
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
const T_IDENTIFIER = 100;
const T_AT = 101;
const T_CLOSE_CURLY_BRACES = 102;
const T_CLOSE_PARENTHESIS = 103;
const T_COMMA = 104;
const T_EQUALS = 105;
const T_FALSE = 106;
const T_NAMESPACE_SEPARATOR = 107;
const T_OPEN_CURLY_BRACES = 108;
const T_OPEN_PARENTHESIS = 109;
const T_TRUE = 110;
const T_NULL = 111;
const T_COLON = 112;
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
*/
protected $noCase = array(
/** @var array<string, int> */
protected $noCase = [
'@' => self::T_AT,
',' => self::T_COMMA,
'(' => self::T_OPEN_PARENTHESIS,
@ -64,28 +49,38 @@ final class DocLexer extends AbstractLexer
'}' => self::T_CLOSE_CURLY_BRACES,
'=' => self::T_EQUALS,
':' => self::T_COLON,
'\\' => self::T_NAMESPACE_SEPARATOR
);
'-' => self::T_MINUS,
'\\' => self::T_NAMESPACE_SEPARATOR,
];
/**
* @var array
*/
protected $withCase = array(
/** @var array<string, int> */
protected $withCase = [
'true' => self::T_TRUE,
'false' => self::T_FALSE,
'null' => self::T_NULL
);
'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 array(
return [
'[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*',
'(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?',
'"(?:""|[^"])*+"',
);
];
}
/**
@ -93,7 +88,7 @@ final class DocLexer extends AbstractLexer
*/
protected function getNonCatchablePatterns()
{
return array('\s+', '\*+', '(.)');
return ['\s+', '\*+', '(.)'];
}
/**
@ -125,7 +120,7 @@ final class DocLexer extends AbstractLexer
// Checking numeric value
if (is_numeric($value)) {
return (strpos($value, '.') !== false || stripos($value, 'e') !== false)
return strpos($value, '.') !== false || stripos($value, 'e') !== false
? self::T_FLOAT : self::T_INTEGER;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,89 +1,84 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*
* @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
*/
/** @var Reader */
private $reader;
/**
* @var string
*/
/** @var string */
private $dir;
/**
* @var bool
*/
/** @var bool */
private $debug;
/**
* @var array
*/
private $loadedAnnotations = array();
/** @phpstan-var array<string, list<object>> */
private $loadedAnnotations = [];
/**
* @var array
*/
private $classNameHashes = array();
/** @var array<string, string> */
private $classNameHashes = [];
/**
* @var int
*/
/** @var int */
private $umask;
/**
* Constructor.
* @param string $cacheDir
* @param bool $debug
* @param int $umask
*
* @param Reader $reader
* @param string $cacheDir
* @param boolean $debug
*
* @throws \InvalidArgumentException
* @throws InvalidArgumentException
*/
public function __construct(Reader $reader, $cacheDir, $debug = false, $umask = 0002)
{
if ( ! is_int($umask)) {
throw new \InvalidArgumentException(sprintf(
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;
$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));
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, '\\/');
@ -93,31 +88,37 @@ class FileCacheReader implements Reader
/**
* {@inheritDoc}
*/
public function getClassAnnotations(\ReflectionClass $class)
public function getClassAnnotations(ReflectionClass $class)
{
if ( ! isset($this->classNameHashes[$class->name])) {
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)) {
$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;
}
if ($this->debug
&& (false !== $filename = $class->getFileName())
&& filemtime($path) < filemtime($filename)) {
$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;
}
@ -127,32 +128,38 @@ class FileCacheReader implements Reader
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(\ReflectionProperty $property)
public function getPropertyAnnotations(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
if ( ! isset($this->classNameHashes[$class->name])) {
if (! isset($this->classNameHashes[$class->name])) {
$this->classNameHashes[$class->name] = sha1($class->name);
}
$key = $this->classNameHashes[$class->name].'$'.$property->getName();
$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)) {
$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;
}
if ($this->debug
&& (false !== $filename = $class->getFilename())
&& filemtime($path) < filemtime($filename)) {
$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;
}
@ -162,32 +169,38 @@ class FileCacheReader implements Reader
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(\ReflectionMethod $method)
public function getMethodAnnotations(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
if ( ! isset($this->classNameHashes[$class->name])) {
if (! isset($this->classNameHashes[$class->name])) {
$this->classNameHashes[$class->name] = sha1($class->name);
}
$key = $this->classNameHashes[$class->name].'#'.$method->getName();
$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)) {
$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;
}
if ($this->debug
&& (false !== $filename = $class->getFilename())
&& filemtime($path) < filemtime($filename)) {
$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;
}
@ -204,36 +217,48 @@ class FileCacheReader implements Reader
*/
private function saveCacheFile($path, $data)
{
if (!is_writable($this->dir)) {
throw new \InvalidArgumentException(sprintf('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.', $this->dir));
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 (false === $tempfile) {
throw new \RuntimeException(sprintf('Unable to create tempfile in directory: %s', $this->dir));
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).');');
$written = file_put_contents(
$tempfile,
'<?php return unserialize(' . var_export(serialize($data), true) . ');'
);
if (false === $written) {
throw new \RuntimeException(sprintf('Unable to write cached file to: %s', $tempfile));
if ($written === false) {
throw new RuntimeException(sprintf('Unable to write cached file to: %s', $tempfile));
}
@chmod($tempfile, 0666 & (~$this->umask));
if (false === rename($tempfile, $path)) {
if (rename($tempfile, $path) === false) {
@unlink($tempfile);
throw new \RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path));
throw new RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path));
}
}
/**
* {@inheritDoc}
*/
public function getClassAnnotation(\ReflectionClass $class, $annotationName)
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
$annotations = $this->getClassAnnotations($class);
@ -249,7 +274,7 @@ class FileCacheReader implements Reader
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(\ReflectionMethod $method, $annotationName)
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
$annotations = $this->getMethodAnnotations($method);
@ -265,7 +290,7 @@ class FileCacheReader implements Reader
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName)
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
$annotations = $this->getPropertyAnnotations($property);
@ -285,6 +310,6 @@ class FileCacheReader implements Reader
*/
public function clearLoadedAnnotations()
{
$this->loadedAnnotations = array();
$this->loadedAnnotations = [];
}
}

View File

@ -1,41 +1,22 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class IndexedReader implements Reader
{
/**
* @var Reader
*/
/** @var Reader */
private $delegate;
/**
* Constructor.
*
* @param Reader $reader
*/
public function __construct(Reader $reader)
{
$this->delegate = $reader;
@ -44,9 +25,9 @@ class IndexedReader implements Reader
/**
* {@inheritDoc}
*/
public function getClassAnnotations(\ReflectionClass $class)
public function getClassAnnotations(ReflectionClass $class)
{
$annotations = array();
$annotations = [];
foreach ($this->delegate->getClassAnnotations($class) as $annot) {
$annotations[get_class($annot)] = $annot;
}
@ -57,7 +38,7 @@ class IndexedReader implements Reader
/**
* {@inheritDoc}
*/
public function getClassAnnotation(\ReflectionClass $class, $annotation)
public function getClassAnnotation(ReflectionClass $class, $annotation)
{
return $this->delegate->getClassAnnotation($class, $annotation);
}
@ -65,9 +46,9 @@ class IndexedReader implements Reader
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(\ReflectionMethod $method)
public function getMethodAnnotations(ReflectionMethod $method)
{
$annotations = array();
$annotations = [];
foreach ($this->delegate->getMethodAnnotations($method) as $annot) {
$annotations[get_class($annot)] = $annot;
}
@ -78,7 +59,7 @@ class IndexedReader implements Reader
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(\ReflectionMethod $method, $annotation)
public function getMethodAnnotation(ReflectionMethod $method, $annotation)
{
return $this->delegate->getMethodAnnotation($method, $annotation);
}
@ -86,9 +67,9 @@ class IndexedReader implements Reader
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(\ReflectionProperty $property)
public function getPropertyAnnotations(ReflectionProperty $property)
{
$annotations = array();
$annotations = [];
foreach ($this->delegate->getPropertyAnnotations($property) as $annot) {
$annotations[get_class($annot)] = $annot;
}
@ -99,7 +80,7 @@ class IndexedReader implements Reader
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(\ReflectionProperty $property, $annotation)
public function getPropertyAnnotation(ReflectionProperty $property, $annotation)
{
return $this->delegate->getPropertyAnnotation($property, $annotation);
}
@ -107,13 +88,13 @@ class IndexedReader implements Reader
/**
* Proxies all methods to the delegate.
*
* @param string $method
* @param array $args
* @param string $method
* @param mixed[] $args
*
* @return mixed
*/
public function __call($method, $args)
{
return call_user_func_array(array($this->delegate, $method), $args);
return call_user_func_array([$this->delegate, $method], $args);
}
}

View File

@ -1,85 +1,86 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christian Kaps <christian.kaps@mohiva.com>
*/
final class PhpParser
{
/**
* Parses a class.
*
* @param \ReflectionClass $class A <code>ReflectionClass</code> object.
* @deprecated use parseUseStatements instead
*
* @return array A list with use statements in the form (Alias => FQN).
* @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)
public function parseClass(ReflectionClass $class)
{
if (method_exists($class, 'getUseStatements')) {
return $class->getUseStatements();
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();
}
if (false === $filename = $class->getFileName()) {
return array();
$filename = $reflection->getFileName();
if ($filename === false) {
return [];
}
$content = $this->getFileContent($filename, $class->getStartLine());
$content = $this->getFileContent($filename, $reflection->getStartLine());
if (null === $content) {
return array();
if ($content === null) {
return [];
}
$namespace = preg_quote($class->getNamespaceName());
$content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content);
$namespace = preg_quote($reflection->getNamespaceName());
$content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content);
$tokenizer = new TokenParser('<?php ' . $content);
$statements = $tokenizer->parseUseStatements($class->getNamespaceName());
return $statements;
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 integer $lineNumber The number of lines to read from file.
* @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)) {
if (! is_file($filename)) {
return null;
}
$content = '';
$lineCnt = 0;
$file = new SplFileObject($filename);
while (!$file->eof()) {
if ($lineCnt++ == $lineNumber) {
$file = new SplFileObject($filename);
while (! $file->eof()) {
if ($lineCnt++ === $lineNumber) {
break;
}

View File

@ -1,89 +1,80 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Annotations;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
/**
* Interface for annotation readers.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
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.
* @param ReflectionClass $class The ReflectionClass of the class from which
* the class annotations should be read.
*
* @return array An array of Annotations.
* @return array<object> An array of Annotations.
*/
function getClassAnnotations(\ReflectionClass $class);
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 string $annotationName The name of the 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 object|null The Annotation or NULL, if the requested annotation does not exist.
* @return T|null The Annotation or NULL, if the requested annotation does not exist.
*
* @template T
*/
function getClassAnnotation(\ReflectionClass $class, $annotationName);
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.
* @param ReflectionMethod $method The ReflectionMethod of the method from which
* the annotations should be read.
*
* @return array An array of Annotations.
* @return array<object> An array of Annotations.
*/
function getMethodAnnotations(\ReflectionMethod $method);
public function getMethodAnnotations(ReflectionMethod $method);
/**
* Gets a method annotation.
*
* @param \ReflectionMethod $method The ReflectionMethod to read the annotations from.
* @param string $annotationName The name of the annotation.
* @param ReflectionMethod $method The ReflectionMethod to read the annotations from.
* @param class-string<T> $annotationName The name of the annotation.
*
* @return object|null The Annotation or NULL, if the requested annotation does not exist.
* @return T|null The Annotation or NULL, if the requested annotation does not exist.
*
* @template T
*/
function getMethodAnnotation(\ReflectionMethod $method, $annotationName);
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.
* @param ReflectionProperty $property The ReflectionProperty of the property
* from which the annotations should be read.
*
* @return array An array of Annotations.
* @return array<object> An array of Annotations.
*/
function getPropertyAnnotations(\ReflectionProperty $property);
public function getPropertyAnnotations(ReflectionProperty $property);
/**
* Gets a property annotation.
*
* @param \ReflectionProperty $property The ReflectionProperty to read the annotations from.
* @param string $annotationName The name of the annotation.
* @param ReflectionProperty $property The ReflectionProperty to read the annotations from.
* @param class-string<T> $annotationName The name of the annotation.
*
* @return object|null The Annotation or NULL, if the requested annotation does not exist.
* @return T|null The Annotation or NULL, if the requested annotation does not exist.
*
* @template T
*/
function getPropertyAnnotation(\ReflectionProperty $property, $annotationName);
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName);
}

View File

@ -1,44 +1,25 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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.
*
* @since 2.2
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @deprecated Deprecated in favour of using AnnotationReader
*/
class SimpleAnnotationReader implements Reader
{
/**
* @var DocParser
*/
/** @var DocParser */
private $parser;
/**
* Constructor.
*
* Initializes a new SimpleAnnotationReader.
*/
public function __construct()
@ -62,31 +43,37 @@ class SimpleAnnotationReader implements Reader
/**
* {@inheritDoc}
*/
public function getClassAnnotations(\ReflectionClass $class)
public function getClassAnnotations(ReflectionClass $class)
{
return $this->parser->parse($class->getDocComment(), 'class '.$class->getName());
return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(\ReflectionMethod $method)
public function getMethodAnnotations(ReflectionMethod $method)
{
return $this->parser->parse($method->getDocComment(), 'method '.$method->getDeclaringClass()->name.'::'.$method->getName().'()');
return $this->parser->parse(
$method->getDocComment(),
'method ' . $method->getDeclaringClass()->name . '::' . $method->getName() . '()'
);
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(\ReflectionProperty $property)
public function getPropertyAnnotations(ReflectionProperty $property)
{
return $this->parser->parse($property->getDocComment(), 'property '.$property->getDeclaringClass()->name.'::$'.$property->getName());
return $this->parser->parse(
$property->getDocComment(),
'property ' . $property->getDeclaringClass()->name . '::$' . $property->getName()
);
}
/**
* {@inheritDoc}
*/
public function getClassAnnotation(\ReflectionClass $class, $annotationName)
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
foreach ($this->getClassAnnotations($class) as $annot) {
if ($annot instanceof $annotationName) {
@ -100,7 +87,7 @@ class SimpleAnnotationReader implements Reader
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(\ReflectionMethod $method, $annotationName)
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
foreach ($this->getMethodAnnotations($method) as $annot) {
if ($annot instanceof $annotationName) {
@ -114,7 +101,7 @@ class SimpleAnnotationReader implements Reader
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName)
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
foreach ($this->getPropertyAnnotations($property) as $annot) {
if ($annot instanceof $annotationName) {

View File

@ -1,36 +1,34 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christian Kaps <christian.kaps@mohiva.com>
*/
class TokenParser
{
/**
* The token list.
*
* @var array
* @phpstan-var list<mixed[]>
*/
private $tokens;
@ -70,19 +68,20 @@ class TokenParser
/**
* Gets the next non whitespace and non comment token.
*
* @param boolean $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped.
* If FALSE then only whitespace and normal comments are skipped.
* @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 array|null The token if exists, null otherwise.
* @return mixed[]|string|null The token if exists, null otherwise.
*/
public function next($docCommentIsComment = TRUE)
public function next($docCommentIsComment = true)
{
for ($i = $this->pointer; $i < $this->numTokens; $i++) {
$this->pointer++;
if ($this->tokens[$i][0] === T_WHITESPACE ||
if (
$this->tokens[$i][0] === T_WHITESPACE ||
$this->tokens[$i][0] === T_COMMENT ||
($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT)) {
($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT)
) {
continue;
}
@ -95,38 +94,47 @@ class TokenParser
/**
* Parses a single use statement.
*
* @return array A list with all found class names for a use statement.
* @return array<string, string> A list with all found class names for a use statement.
*/
public function parseUseStatement()
{
$groupRoot = '';
$class = '';
$alias = '';
$statements = array();
$groupRoot = '';
$class = '';
$alias = '';
$statements = [];
$explicitAlias = false;
while (($token = $this->next())) {
$isNameToken = $token[0] === T_STRING || $token[0] === T_NS_SEPARATOR;
if (!$explicitAlias && $isNameToken) {
if (! $explicitAlias && $token[0] === T_STRING) {
$class .= $token[1];
$alias = $token[1];
} elseif ($explicitAlias && $token[0] === T_STRING) {
$alias = $token[1];
} else if ($explicitAlias && $isNameToken) {
$alias .= $token[1];
} else if ($token[0] === T_AS) {
} 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 = '';
} else if ($token === ',') {
$alias = '';
} elseif ($token === ',') {
$statements[strtolower($alias)] = $groupRoot . $class;
$class = '';
$alias = '';
$explicitAlias = false;
} else if ($token === ';') {
$class = '';
$alias = '';
$explicitAlias = false;
} elseif ($token === ';') {
$statements[strtolower($alias)] = $groupRoot . $class;
break;
} else if ($token === '{' ) {
} elseif ($token === '{') {
$groupRoot = $class;
$class = '';
} else if ($token === '}' ) {
$class = '';
} elseif ($token === '}') {
continue;
} else {
break;
@ -141,24 +149,25 @@ class TokenParser
*
* @param string $namespaceName The namespace name of the reflected class.
*
* @return array A list with all found use statements.
* @return array<string, string> A list with all found use statements.
*/
public function parseUseStatements($namespaceName)
{
$statements = array();
$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) {
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 = array();
$statements = [];
}
return $statements;
@ -172,7 +181,12 @@ class TokenParser
public function parseNamespace()
{
$name = '';
while (($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) {
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];
}

View File

@ -1,14 +1,9 @@
# Doctrine Cache
Master: [![Build Status](https://secure.travis-ci.org/doctrine/cache.png?branch=master)](http://travis-ci.org/doctrine/cache) [![Coverage Status](https://coveralls.io/repos/doctrine/cache/badge.png?branch=master)](https://coveralls.io/r/doctrine/cache?branch=master)
[![Build Status](https://img.shields.io/travis/doctrine/cache/master.svg?style=flat-square)](http://travis-ci.org/doctrine/cache)
[![Code Coverage](https://codecov.io/gh/doctrine/dbal/branch/cache/graph/badge.svg)](https://codecov.io/gh/doctrine/dbal/branch/master)
[![Latest Stable Version](https://poser.pugx.org/doctrine/cache/v/stable.png)](https://packagist.org/packages/doctrine/cache) [![Total Downloads](https://poser.pugx.org/doctrine/cache/downloads.png)](https://packagist.org/packages/doctrine/cache)
[![Latest Stable Version](https://img.shields.io/packagist/v/doctrine/cache.svg?style=flat-square)](https://packagist.org/packages/doctrine/cache)
[![Total Downloads](https://img.shields.io/packagist/dt/doctrine/cache.svg?style=flat-square)](https://packagist.org/packages/doctrine/cache)
Cache component extracted from the Doctrine Common project.
## Changelog
### v1.2
* Added support for MongoDB as Cache Provider
* Fix namespace version reset
Cache component extracted from the Doctrine Common project. [Documentation](https://www.doctrine-project.org/projects/doctrine-cache/en/current/index.html)

View File

@ -1,9 +1,19 @@
{
"name": "doctrine/cache",
"type": "library",
"description": "Caching library offering an object-oriented API for many cache backends",
"keywords": ["cache", "caching"],
"homepage": "http://www.doctrine-project.org",
"description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.",
"keywords": [
"php",
"cache",
"caching",
"abstraction",
"redis",
"memcached",
"couchdb",
"xcache",
"apcu"
],
"homepage": "https://www.doctrine-project.org/projects/cache.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
@ -13,12 +23,17 @@
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
],
"require": {
"php": "~5.5|~7.0"
"php": "~7.1 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "~4.8|~5.0",
"satooshi/php-coveralls": "~0.6",
"predis/predis": "~1.0"
"alcaeus/mongo-php-adapter": "^1.1",
"mongodb/mongodb": "^1.1",
"phpunit/phpunit": "^7.0",
"predis/predis": "~1.0",
"doctrine/coding-standard": "^6.0"
},
"suggest": {
"alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver"
},
"conflict": {
"doctrine/common": ">2.2,<2.4"
@ -31,7 +46,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "1.6.x-dev"
"dev-master": "1.9.x-dev"
}
}
}

View File

@ -1,35 +1,22 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use const PHP_VERSION_ID;
use function apc_cache_info;
use function apc_clear_cache;
use function apc_delete;
use function apc_exists;
use function apc_fetch;
use function apc_sma_info;
use function apc_store;
/**
* APC cache provider.
*
* @link www.doctrine-project.org
* @deprecated since version 1.6, use ApcuCache instead
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*
* @link www.doctrine-project.org
*/
class ApcCache extends CacheProvider
{
@ -102,17 +89,17 @@ class ApcCache extends CacheProvider
// @TODO - Temporary fix @see https://github.com/krakjoe/apcu/pull/42
if (PHP_VERSION_ID >= 50500) {
$info['num_hits'] = isset($info['num_hits']) ? $info['num_hits'] : $info['nhits'];
$info['num_misses'] = isset($info['num_misses']) ? $info['num_misses'] : $info['nmisses'];
$info['start_time'] = isset($info['start_time']) ? $info['start_time'] : $info['stime'];
$info['num_hits'] = $info['num_hits'] ?? $info['nhits'];
$info['num_misses'] = $info['num_misses'] ?? $info['nmisses'];
$info['start_time'] = $info['start_time'] ?? $info['stime'];
}
return array(
return [
Cache::STATS_HITS => $info['num_hits'],
Cache::STATS_MISSES => $info['num_misses'],
Cache::STATS_UPTIME => $info['start_time'],
Cache::STATS_MEMORY_USAGE => $info['mem_size'],
Cache::STATS_MEMORY_AVAILABLE => $sma['avail_mem'],
);
];
}
}

View File

@ -1,30 +1,20 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use function apcu_cache_info;
use function apcu_clear_cache;
use function apcu_delete;
use function apcu_exists;
use function apcu_fetch;
use function apcu_sma_info;
use function apcu_store;
use function count;
/**
* APCu cache provider.
*
* @link www.doctrine-project.org
* @since 1.6
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ApcuCache extends CacheProvider
{
@ -61,6 +51,16 @@ class ApcuCache extends CacheProvider
return apcu_delete($id) || ! apcu_exists($id);
}
/**
* {@inheritdoc}
*/
protected function doDeleteMultiple(array $keys)
{
$result = apcu_delete($keys);
return $result !== false && count($result) !== count($keys);
}
/**
* {@inheritdoc}
*/
@ -95,12 +95,12 @@ class ApcuCache extends CacheProvider
$info = apcu_cache_info(true);
$sma = apcu_sma_info();
return array(
return [
Cache::STATS_HITS => $info['num_hits'],
Cache::STATS_MISSES => $info['num_misses'],
Cache::STATS_UPTIME => $info['start_time'],
Cache::STATS_MEMORY_USAGE => $info['mem_size'],
Cache::STATS_MEMORY_AVAILABLE => $sma['avail_mem'],
);
];
}
}

View File

@ -1,55 +1,26 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use function time;
/**
* Array cache driver.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class ArrayCache extends CacheProvider
{
/**
* @var array[] $data each element being a tuple of [$data, $expiration], where the expiration is int|bool
*/
/** @var array[] $data each element being a tuple of [$data, $expiration], where the expiration is int|bool */
private $data = [];
/**
* @var int
*/
/** @var int */
private $hitsCount = 0;
/**
* @var int
*/
/** @var int */
private $missesCount = 0;
/**
* @var int
*/
/** @var int */
private $upTime;
/**

View File

@ -1,21 +1,4 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
@ -23,27 +6,20 @@ namespace Doctrine\Common\Cache;
* Interface for cache drivers.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface Cache
{
const STATS_HITS = 'hits';
const STATS_MISSES = 'misses';
const STATS_UPTIME = 'uptime';
const STATS_MEMORY_USAGE = 'memory_usage';
const STATS_MEMORY_AVAILABLE = 'memory_available';
public const STATS_HITS = 'hits';
public const STATS_MISSES = 'misses';
public const STATS_UPTIME = 'uptime';
public const STATS_MEMORY_USAGE = 'memory_usage';
public const STATS_MEMORY_AVAILABLE = 'memory_available';
/**
* Only for backward compatibility (may be removed in next major release)
*
* @deprecated
*/
const STATS_MEMORY_AVAILIABLE = 'memory_available';
public const STATS_MEMORY_AVAILIABLE = 'memory_available';
/**
* Fetches an entry from the cache.
@ -108,8 +84,6 @@ interface Cache
* - <b>memory_available</b>
* Memory allowed to use for storage.
*
* @since 2.2
*
* @return array|null An associative array with server's statistics if available, NULL otherwise.
*/
public function getStats();

View File

@ -1,37 +1,18 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use function array_combine;
use function array_key_exists;
use function array_map;
use function sprintf;
/**
* Base class for cache provider implementations.
*
* @since 2.2
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, MultiGetCache, MultiPutCache
abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, MultiOperationCache
{
const DOCTRINE_NAMESPACE_CACHEKEY = 'DoctrineNamespaceCacheKey[%s]';
public const DOCTRINE_NAMESPACE_CACHEKEY = 'DoctrineNamespaceCacheKey[%s]';
/**
* The namespace to prefix all cache ids with.
@ -43,7 +24,7 @@ abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, M
/**
* The namespace version.
*
* @var integer|null
* @var int|null
*/
private $namespaceVersion;
@ -84,20 +65,22 @@ abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, M
public function fetchMultiple(array $keys)
{
if (empty($keys)) {
return array();
return [];
}
// note: the array_combine() is in place to keep an association between our $keys and the $namespacedKeys
$namespacedKeys = array_combine($keys, array_map(array($this, 'getNamespacedId'), $keys));
$namespacedKeys = array_combine($keys, array_map([$this, 'getNamespacedId'], $keys));
$items = $this->doFetchMultiple($namespacedKeys);
$foundItems = array();
$foundItems = [];
// no internal array function supports this sort of mapping: needs to be iterative
// this filters and combines keys in one pass
foreach ($namespacedKeys as $requestedKey => $namespacedKey) {
if (isset($items[$namespacedKey]) || array_key_exists($namespacedKey, $items)) {
$foundItems[$requestedKey] = $items[$namespacedKey];
if (! isset($items[$namespacedKey]) && ! array_key_exists($namespacedKey, $items)) {
continue;
}
$foundItems[$requestedKey] = $items[$namespacedKey];
}
return $foundItems;
@ -108,7 +91,7 @@ abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, M
*/
public function saveMultiple(array $keysAndValues, $lifetime = 0)
{
$namespacedKeysAndValues = array();
$namespacedKeysAndValues = [];
foreach ($keysAndValues as $key => $value) {
$namespacedKeysAndValues[$this->getNamespacedId($key)] = $value;
}
@ -132,6 +115,14 @@ abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, M
return $this->doSave($this->getNamespacedId($id), $data, $lifeTime);
}
/**
* {@inheritdoc}
*/
public function deleteMultiple(array $keys)
{
return $this->doDeleteMultiple(array_map([$this, 'getNamespacedId'], $keys));
}
/**
* {@inheritdoc}
*/
@ -180,36 +171,32 @@ abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, M
*
* @return string The namespaced id.
*/
private function getNamespacedId($id)
private function getNamespacedId(string $id) : string
{
$namespaceVersion = $this->getNamespaceVersion();
$namespaceVersion = $this->getNamespaceVersion();
return sprintf('%s[%s][%s]', $this->namespace, $id, $namespaceVersion);
}
/**
* Returns the namespace cache key.
*
* @return string
*/
private function getNamespaceCacheKey()
private function getNamespaceCacheKey() : string
{
return sprintf(self::DOCTRINE_NAMESPACE_CACHEKEY, $this->namespace);
}
/**
* Returns the namespace version.
*
* @return integer
*/
private function getNamespaceVersion()
private function getNamespaceVersion() : int
{
if (null !== $this->namespaceVersion) {
if ($this->namespaceVersion !== null) {
return $this->namespaceVersion;
}
$namespaceCacheKey = $this->getNamespaceCacheKey();
$this->namespaceVersion = $this->doFetch($namespaceCacheKey) ?: 1;
$namespaceCacheKey = $this->getNamespaceCacheKey();
$this->namespaceVersion = (int) $this->doFetch($namespaceCacheKey) ?: 1;
return $this->namespaceVersion;
}
@ -218,16 +205,20 @@ abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, M
* Default implementation of doFetchMultiple. Each driver that supports multi-get should owerwrite it.
*
* @param array $keys Array of keys to retrieve from cache
*
* @return array Array of values retrieved for the given keys.
*/
protected function doFetchMultiple(array $keys)
{
$returnValues = array();
$returnValues = [];
foreach ($keys as $key) {
if (false !== ($item = $this->doFetch($key)) || $this->doContains($key)) {
$returnValues[$key] = $item;
$item = $this->doFetch($key);
if ($item === false && ! $this->doContains($key)) {
continue;
}
$returnValues[$key] = $item;
}
return $returnValues;
@ -254,9 +245,9 @@ abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, M
/**
* Default implementation of doSaveMultiple. Each driver that supports multi-put should override it.
*
* @param array $keysAndValues Array of keys and values to save in cache
* @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these
* cache entries (0 => infinite lifeTime).
* @param array $keysAndValues Array of keys and values to save in cache
* @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these
* cache entries (0 => infinite lifeTime).
*
* @return bool TRUE if the operation was successful, FALSE if it wasn't.
*/
@ -265,9 +256,11 @@ abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, M
$success = true;
foreach ($keysAndValues as $key => $value) {
if (!$this->doSave($key, $value, $lifetime)) {
$success = false;
if ($this->doSave($key, $value, $lifetime)) {
continue;
}
$success = false;
}
return $success;
@ -285,6 +278,28 @@ abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, M
*/
abstract protected function doSave($id, $data, $lifeTime = 0);
/**
* Default implementation of doDeleteMultiple. Each driver that supports multi-delete should override it.
*
* @param array $keys Array of keys to delete from cache
*
* @return bool TRUE if the operation was successful, FALSE if it wasn't
*/
protected function doDeleteMultiple(array $keys)
{
$success = true;
foreach ($keys as $key) {
if ($this->doDelete($key)) {
continue;
}
$success = false;
}
return $success;
}
/**
* Deletes a cache entry.
*
@ -304,8 +319,6 @@ abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, M
/**
* Retrieves cached information from the data store.
*
* @since 2.2
*
* @return array|null An associative array with server's statistics if available, NULL otherwise.
*/
abstract protected function doGetStats();

View File

@ -1,44 +1,36 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use Traversable;
use function array_values;
use function count;
use function iterator_to_array;
/**
* Cache provider that allows to easily chain multiple cache providers
*
* @author Michaël Gallego <mic.gallego@gmail.com>
*/
class ChainCache extends CacheProvider
{
/**
* @var CacheProvider[]
*/
private $cacheProviders = array();
/** @var CacheProvider[] */
private $cacheProviders = [];
/** @var int */
private $defaultLifeTimeForDownstreamCacheProviders = 0;
/**
* Constructor
*
* @param CacheProvider[] $cacheProviders
*/
public function __construct($cacheProviders = array())
public function __construct($cacheProviders = [])
{
$this->cacheProviders = $cacheProviders;
$this->cacheProviders = $cacheProviders instanceof Traversable
? iterator_to_array($cacheProviders, false)
: array_values($cacheProviders);
}
public function setDefaultLifeTimeForDownstreamCacheProviders(int $defaultLifeTimeForDownstreamCacheProviders) : void
{
$this->defaultLifeTimeForDownstreamCacheProviders = $defaultLifeTimeForDownstreamCacheProviders;
}
/**
@ -63,8 +55,8 @@ class ChainCache extends CacheProvider
$value = $cacheProvider->doFetch($id);
// We populate all the previous cache layers (that are assumed to be faster)
for ($subKey = $key - 1 ; $subKey >= 0 ; $subKey--) {
$this->cacheProviders[$subKey]->doSave($id, $value);
for ($subKey = $key - 1; $subKey >= 0; $subKey--) {
$this->cacheProviders[$subKey]->doSave($id, $value, $this->defaultLifeTimeForDownstreamCacheProviders);
}
return $value;
@ -74,6 +66,34 @@ class ChainCache extends CacheProvider
return false;
}
/**
* {@inheritdoc}
*/
protected function doFetchMultiple(array $keys)
{
/** @var CacheProvider[] $traversedProviders */
$traversedProviders = [];
$keysCount = count($keys);
$fetchedValues = [];
foreach ($this->cacheProviders as $key => $cacheProvider) {
$fetchedValues = $cacheProvider->doFetchMultiple($keys);
// We populate all the previous cache layers (that are assumed to be faster)
if (count($fetchedValues) === $keysCount) {
foreach ($traversedProviders as $previousCacheProvider) {
$previousCacheProvider->doSaveMultiple($fetchedValues, $this->defaultLifeTimeForDownstreamCacheProviders);
}
return $fetchedValues;
}
$traversedProviders[] = $cacheProvider;
}
return $fetchedValues;
}
/**
* {@inheritDoc}
*/
@ -102,6 +122,20 @@ class ChainCache extends CacheProvider
return $stored;
}
/**
* {@inheritdoc}
*/
protected function doSaveMultiple(array $keysAndValues, $lifetime = 0)
{
$stored = true;
foreach ($this->cacheProviders as $cacheProvider) {
$stored = $cacheProvider->doSaveMultiple($keysAndValues, $lifetime) && $stored;
}
return $stored;
}
/**
* {@inheritDoc}
*/
@ -116,6 +150,20 @@ class ChainCache extends CacheProvider
return $deleted;
}
/**
* {@inheritdoc}
*/
protected function doDeleteMultiple(array $keys)
{
$deleted = true;
foreach ($this->cacheProviders as $cacheProvider) {
$deleted = $cacheProvider->doDeleteMultiple($keys) && $deleted;
}
return $deleted;
}
/**
* {@inheritDoc}
*/
@ -136,7 +184,7 @@ class ChainCache extends CacheProvider
protected function doGetStats()
{
// We return all the stats from all adapters
$stats = array();
$stats = [];
foreach ($this->cacheProviders as $cacheProvider) {
$stats[] = $cacheProvider->doGetStats();

View File

@ -1,21 +1,4 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
@ -26,8 +9,6 @@ namespace Doctrine\Common\Cache;
* global "flushing", see {@see FlushableCache}.
*
* @link www.doctrine-project.org
* @since 1.4
* @author Adirelle <adirelle@gmail.com>
*/
interface ClearableCache
{

View File

@ -1,45 +1,27 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use \Couchbase;
use Couchbase;
use function explode;
use function time;
/**
* Couchbase cache provider.
*
* @deprecated Couchbase SDK 1.x is now deprecated. Use \Doctrine\Common\Cache\CouchbaseBucketCache instead.
* https://developer.couchbase.com/documentation/server/current/sdk/php/compatibility-versions-features.html
*
* @link www.doctrine-project.org
* @since 2.4
* @author Michael Nitschinger <michael@nitschinger.at>
*/
class CouchbaseCache extends CacheProvider
{
/**
* @var Couchbase|null
*/
/** @var Couchbase|null */
private $couchbase;
/**
* Sets the Couchbase instance to use.
*
* @param Couchbase $couchbase
*
* @return void
*/
public function setCouchbase(Couchbase $couchbase)
@ -70,7 +52,7 @@ class CouchbaseCache extends CacheProvider
*/
protected function doContains($id)
{
return (null !== $this->couchbase->get($id));
return $this->couchbase->get($id) !== null;
}
/**
@ -81,6 +63,7 @@ class CouchbaseCache extends CacheProvider
if ($lifeTime > 30 * 24 * 3600) {
$lifeTime = time() + $lifeTime;
}
return $this->couchbase->set($id, $data, (int) $lifeTime);
}
@ -107,15 +90,16 @@ class CouchbaseCache extends CacheProvider
{
$stats = $this->couchbase->getStats();
$servers = $this->couchbase->getServers();
$server = explode(":", $servers[0]);
$key = $server[0] . ":" . "11210";
$server = explode(':', $servers[0]);
$key = $server[0] . ':11210';
$stats = $stats[$key];
return array(
return [
Cache::STATS_HITS => $stats['get_hits'],
Cache::STATS_MISSES => $stats['get_misses'],
Cache::STATS_UPTIME => $stats['uptime'],
Cache::STATS_MEMORY_USAGE => $stats['bytes'],
Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'],
);
];
}
}

View File

@ -1,30 +1,39 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use FilesystemIterator;
use InvalidArgumentException;
use Iterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use const DIRECTORY_SEPARATOR;
use const PATHINFO_DIRNAME;
use function bin2hex;
use function chmod;
use function defined;
use function disk_free_space;
use function file_exists;
use function file_put_contents;
use function gettype;
use function hash;
use function is_dir;
use function is_int;
use function is_writable;
use function mkdir;
use function pathinfo;
use function realpath;
use function rename;
use function rmdir;
use function sprintf;
use function strlen;
use function strrpos;
use function substr;
use function tempnam;
use function unlink;
/**
* Base file cache driver.
*
* @since 2.3
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @author Tobias Schultze <http://tobion.de>
*/
abstract class FileCache extends CacheProvider
{
@ -42,54 +51,44 @@ abstract class FileCache extends CacheProvider
*/
private $extension;
/**
* @var int
*/
/** @var int */
private $umask;
/**
* @var int
*/
/** @var int */
private $directoryStringLength;
/**
* @var int
*/
/** @var int */
private $extensionStringLength;
/**
* @var bool
*/
/** @var bool */
private $isRunningOnWindows;
/**
* Constructor.
*
* @param string $directory The cache directory.
* @param string $extension The cache file extension.
*
* @throws \InvalidArgumentException
* @throws InvalidArgumentException
*/
public function __construct($directory, $extension = '', $umask = 0002)
{
// YES, this needs to be *before* createPathIfNeeded()
if ( ! is_int($umask)) {
throw new \InvalidArgumentException(sprintf(
if (! is_int($umask)) {
throw new InvalidArgumentException(sprintf(
'The umask parameter is required to be integer, was: %s',
gettype($umask)
));
}
$this->umask = $umask;
if ( ! $this->createPathIfNeeded($directory)) {
throw new \InvalidArgumentException(sprintf(
if (! $this->createPathIfNeeded($directory)) {
throw new InvalidArgumentException(sprintf(
'The directory "%s" does not exist and could not be created.',
$directory
));
}
if ( ! is_writable($directory)) {
throw new \InvalidArgumentException(sprintf(
if (! is_writable($directory)) {
throw new InvalidArgumentException(sprintf(
'The directory "%s" is not writable.',
$directory
));
@ -134,8 +133,7 @@ abstract class FileCache extends CacheProvider
$hash = hash('sha256', $id);
// This ensures that the filename is unique and that there are no invalid chars in it.
if (
'' === $id
if ($id === ''
|| ((strlen($id) * 2 + $this->extensionStringLength) > 255)
|| ($this->isRunningOnWindows && ($this->directoryStringLength + 4 + strlen($id) * 2 + $this->extensionStringLength) > 258)
) {
@ -195,32 +193,33 @@ abstract class FileCache extends CacheProvider
{
$usage = 0;
foreach ($this->getIterator() as $name => $file) {
if (! $file->isDir() && $this->isFilenameEndingWithExtension($name)) {
$usage += $file->getSize();
if ($file->isDir() || ! $this->isFilenameEndingWithExtension($name)) {
continue;
}
$usage += $file->getSize();
}
$free = disk_free_space($this->directory);
return array(
return [
Cache::STATS_HITS => null,
Cache::STATS_MISSES => null,
Cache::STATS_UPTIME => null,
Cache::STATS_MEMORY_USAGE => $usage,
Cache::STATS_MEMORY_AVAILABLE => $free,
);
];
}
/**
* Create path if needed.
*
* @param string $path
* @return bool TRUE on success or if path already exists, FALSE if path cannot be created.
*/
private function createPathIfNeeded($path)
private function createPathIfNeeded(string $path) : bool
{
if ( ! is_dir($path)) {
if (false === @mkdir($path, 0777 & (~$this->umask), true) && !is_dir($path)) {
if (! is_dir($path)) {
if (@mkdir($path, 0777 & (~$this->umask), true) === false && ! is_dir($path)) {
return false;
}
}
@ -236,15 +235,15 @@ abstract class FileCache extends CacheProvider
*
* @return bool TRUE on success, FALSE if path cannot be created, if path is not writable or an any other error.
*/
protected function writeFile($filename, $content)
protected function writeFile(string $filename, string $content) : bool
{
$filepath = pathinfo($filename, PATHINFO_DIRNAME);
if ( ! $this->createPathIfNeeded($filepath)) {
if (! $this->createPathIfNeeded($filepath)) {
return false;
}
if ( ! is_writable($filepath)) {
if (! is_writable($filepath)) {
return false;
}
@ -252,6 +251,7 @@ abstract class FileCache extends CacheProvider
@chmod($tmpFile, 0666 & (~$this->umask));
if (file_put_contents($tmpFile, $content) !== false) {
@chmod($tmpFile, 0666 & (~$this->umask));
if (@rename($tmpFile, $filename)) {
return true;
}
@ -262,25 +262,20 @@ abstract class FileCache extends CacheProvider
return false;
}
/**
* @return \Iterator
*/
private function getIterator()
private function getIterator() : Iterator
{
return new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
return new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->directory, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
}
/**
* @param string $name The filename
*
* @return bool
*/
private function isFilenameEndingWithExtension($name)
private function isFilenameEndingWithExtension(string $name) : bool
{
return '' === $this->extension
return $this->extension === ''
|| strrpos($name, $this->extension) === (strlen($name) - $this->extensionStringLength);
}
}

View File

@ -1,33 +1,22 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use const PHP_EOL;
use function fclose;
use function fgets;
use function fopen;
use function is_file;
use function serialize;
use function time;
use function unserialize;
/**
* Filesystem cache driver.
*
* @since 2.3
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class FilesystemCache extends FileCache
{
const EXTENSION = '.doctrinecache.data';
public const EXTENSION = '.doctrinecache.data';
/**
* {@inheritdoc}
@ -46,13 +35,14 @@ class FilesystemCache extends FileCache
$lifetime = -1;
$filename = $this->getFilename($id);
if ( ! is_file($filename)) {
if (! is_file($filename)) {
return false;
}
$resource = fopen($filename, "r");
$resource = fopen($filename, 'r');
$line = fgets($resource);
if (false !== ($line = fgets($resource))) {
if ($line !== false) {
$lifetime = (int) $line;
}
@ -62,7 +52,7 @@ class FilesystemCache extends FileCache
return false;
}
while (false !== ($line = fgets($resource))) {
while (($line = fgets($resource)) !== false) {
$data .= $line;
}
@ -79,13 +69,14 @@ class FilesystemCache extends FileCache
$lifetime = -1;
$filename = $this->getFilename($id);
if ( ! is_file($filename)) {
if (! is_file($filename)) {
return false;
}
$resource = fopen($filename, "r");
$resource = fopen($filename, 'r');
$line = fgets($resource);
if (false !== ($line = fgets($resource))) {
if ($line !== false) {
$lifetime = (int) $line;
}
@ -103,8 +94,8 @@ class FilesystemCache extends FileCache
$lifeTime = time() + $lifeTime;
}
$data = serialize($data);
$filename = $this->getFilename($id);
$data = serialize($data);
$filename = $this->getFilename($id);
return $this->writeFile($filename, $lifeTime . PHP_EOL . $data);
}

View File

@ -1,21 +1,4 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
@ -23,8 +6,6 @@ namespace Doctrine\Common\Cache;
* Interface for cache that can be flushed.
*
* @link www.doctrine-project.org
* @since 1.4
* @author Adirelle <adirelle@gmail.com>
*/
interface FlushableCache
{

View File

@ -1,49 +1,25 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use \Memcache;
use Memcache;
use function time;
/**
* Memcache cache provider.
*
* @deprecated
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class MemcacheCache extends CacheProvider
{
/**
* @var Memcache|null
*/
/** @var Memcache|null */
private $memcache;
/**
* Sets the memcache instance to use.
*
* @param Memcache $memcache
*
* @return void
*/
public function setMemcache(Memcache $memcache)
@ -76,9 +52,9 @@ class MemcacheCache extends CacheProvider
{
$flags = null;
$this->memcache->get($id, $flags);
//if memcache has changed the value of "flags", it means the value exists
return ($flags !== null);
return $flags !== null;
}
/**
@ -89,6 +65,7 @@ class MemcacheCache extends CacheProvider
if ($lifeTime > 30 * 24 * 3600) {
$lifeTime = time() + $lifeTime;
}
return $this->memcache->set($id, $data, 0, (int) $lifeTime);
}
@ -115,12 +92,13 @@ class MemcacheCache extends CacheProvider
protected function doGetStats()
{
$stats = $this->memcache->getStats();
return array(
return [
Cache::STATS_HITS => $stats['get_hits'],
Cache::STATS_MISSES => $stats['get_misses'],
Cache::STATS_UPTIME => $stats['uptime'],
Cache::STATS_MEMORY_USAGE => $stats['bytes'],
Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'],
);
];
}
}

View File

@ -1,49 +1,29 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use \Memcached;
use Memcached;
use function array_keys;
use function preg_match;
use function strlen;
use function strpos;
use function time;
/**
* Memcached cache provider.
*
* @link www.doctrine-project.org
* @since 2.2
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class MemcachedCache extends CacheProvider
{
/**
* @var Memcached|null
*/
public const CACHE_ID_MAX_LENGTH = 250;
/** @var Memcached|null */
private $memcached;
/**
* Sets the memcache instance to use.
*
* @param Memcached $memcached
*
* @return void
*/
public function setMemcached(Memcached $memcached)
@ -82,6 +62,10 @@ class MemcachedCache extends CacheProvider
*/
protected function doSaveMultiple(array $keysAndValues, $lifetime = 0)
{
foreach (array_keys($keysAndValues) as $id) {
$this->validateCacheId($id);
}
if ($lifetime > 30 * 24 * 3600) {
$lifetime = time() + $lifetime;
}
@ -104,12 +88,24 @@ class MemcachedCache extends CacheProvider
*/
protected function doSave($id, $data, $lifeTime = 0)
{
$this->validateCacheId($id);
if ($lifeTime > 30 * 24 * 3600) {
$lifeTime = time() + $lifeTime;
}
return $this->memcached->set($id, $data, (int) $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDeleteMultiple(array $keys)
{
return $this->memcached->deleteMulti($keys)
|| $this->memcached->getResultCode() === Memcached::RES_NOTFOUND;
}
/**
* {@inheritdoc}
*/
@ -136,12 +132,39 @@ class MemcachedCache extends CacheProvider
$servers = $this->memcached->getServerList();
$key = $servers[0]['host'] . ':' . $servers[0]['port'];
$stats = $stats[$key];
return array(
return [
Cache::STATS_HITS => $stats['get_hits'],
Cache::STATS_MISSES => $stats['get_misses'],
Cache::STATS_UPTIME => $stats['uptime'],
Cache::STATS_MEMORY_USAGE => $stats['bytes'],
Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'],
);
];
}
/**
* Validate the cache id
*
* @see https://github.com/memcached/memcached/blob/1.5.12/doc/protocol.txt#L41-L49
*
* @param string $id
*
* @return void
*
* @throws InvalidCacheId
*/
private function validateCacheId($id)
{
if (strlen($id) > self::CACHE_ID_MAX_LENGTH) {
throw InvalidCacheId::exceedsMaxLength($id, self::CACHE_ID_MAX_LENGTH);
}
if (strpos($id, ' ') !== false) {
throw InvalidCacheId::containsUnauthorizedCharacter($id, ' ');
}
if (preg_match('/[\t\r\n]/', $id) === 1) {
throw InvalidCacheId::containsControlCharacter($id);
}
}
}

View File

@ -1,41 +1,22 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use MongoBinData;
use InvalidArgumentException;
use MongoCollection;
use MongoCursorException;
use MongoDate;
use MongoDB\Collection;
use const E_USER_DEPRECATED;
use function trigger_error;
/**
* MongoDB cache provider.
*
* @since 1.1
* @author Jeremy Mikola <jmikola@gmail.com>
*/
class MongoDBCache extends CacheProvider
{
/**
* The data field will store the serialized PHP value.
*/
const DATA_FIELD = 'd';
public const DATA_FIELD = 'd';
/**
* The expiration field will store a MongoDate value indicating when the
@ -52,29 +33,33 @@ class MongoDBCache extends CacheProvider
*
* @see http://docs.mongodb.org/manual/tutorial/expire-data/
*/
const EXPIRATION_FIELD = 'e';
public const EXPIRATION_FIELD = 'e';
/** @var CacheProvider */
private $provider;
/**
* @var MongoCollection
*/
private $collection;
/**
* Constructor.
*
* This provider will default to the write concern and read preference
* options set on the MongoCollection instance (or inherited from MongoDB or
* options set on the collection instance (or inherited from MongoDB or
* MongoClient). Using an unacknowledged write concern (< 1) may make the
* return values of delete() and save() unreliable. Reading from secondaries
* may make contain() and fetch() unreliable.
*
* @see http://www.php.net/manual/en/mongo.readpreferences.php
* @see http://www.php.net/manual/en/mongo.writeconcerns.php
* @param MongoCollection $collection
*
* @param MongoCollection|Collection $collection
*/
public function __construct(MongoCollection $collection)
public function __construct($collection)
{
$this->collection = $collection;
if ($collection instanceof MongoCollection) {
@trigger_error('Using a MongoCollection instance for creating a cache adapter is deprecated and will be removed in 2.0', E_USER_DEPRECATED);
$this->provider = new LegacyMongoDBCache($collection);
} elseif ($collection instanceof Collection) {
$this->provider = new ExtMongoDBCache($collection);
} else {
throw new InvalidArgumentException('Invalid collection given - expected a MongoCollection or MongoDB\Collection instance');
}
}
/**
@ -82,18 +67,7 @@ class MongoDBCache extends CacheProvider
*/
protected function doFetch($id)
{
$document = $this->collection->findOne(array('_id' => $id), array(self::DATA_FIELD, self::EXPIRATION_FIELD));
if ($document === null) {
return false;
}
if ($this->isExpired($document)) {
$this->doDelete($id);
return false;
}
return unserialize($document[self::DATA_FIELD]->bin);
return $this->provider->doFetch($id);
}
/**
@ -101,18 +75,7 @@ class MongoDBCache extends CacheProvider
*/
protected function doContains($id)
{
$document = $this->collection->findOne(array('_id' => $id), array(self::EXPIRATION_FIELD));
if ($document === null) {
return false;
}
if ($this->isExpired($document)) {
$this->doDelete($id);
return false;
}
return true;
return $this->provider->doContains($id);
}
/**
@ -120,20 +83,7 @@ class MongoDBCache extends CacheProvider
*/
protected function doSave($id, $data, $lifeTime = 0)
{
try {
$result = $this->collection->update(
array('_id' => $id),
array('$set' => array(
self::EXPIRATION_FIELD => ($lifeTime > 0 ? new MongoDate(time() + $lifeTime) : null),
self::DATA_FIELD => new MongoBinData(serialize($data), MongoBinData::BYTE_ARRAY),
)),
array('upsert' => true, 'multiple' => false)
);
} catch (MongoCursorException $e) {
return false;
}
return isset($result['ok']) ? $result['ok'] == 1 : true;
return $this->provider->doSave($id, $data, $lifeTime);
}
/**
@ -141,9 +91,7 @@ class MongoDBCache extends CacheProvider
*/
protected function doDelete($id)
{
$result = $this->collection->remove(array('_id' => $id));
return isset($result['ok']) ? $result['ok'] == 1 : true;
return $this->provider->doDelete($id);
}
/**
@ -151,10 +99,7 @@ class MongoDBCache extends CacheProvider
*/
protected function doFlush()
{
// Use remove() in lieu of drop() to maintain any collection indexes
$result = $this->collection->remove();
return isset($result['ok']) ? $result['ok'] == 1 : true;
return $this->provider->doFlush();
}
/**
@ -162,36 +107,6 @@ class MongoDBCache extends CacheProvider
*/
protected function doGetStats()
{
$serverStatus = $this->collection->db->command(array(
'serverStatus' => 1,
'locks' => 0,
'metrics' => 0,
'recordStats' => 0,
'repl' => 0,
));
$collStats = $this->collection->db->command(array('collStats' => 1));
return array(
Cache::STATS_HITS => null,
Cache::STATS_MISSES => null,
Cache::STATS_UPTIME => (isset($serverStatus['uptime']) ? (int) $serverStatus['uptime'] : null),
Cache::STATS_MEMORY_USAGE => (isset($collStats['size']) ? (int) $collStats['size'] : null),
Cache::STATS_MEMORY_AVAILABLE => null,
);
}
/**
* Check if the document is expired.
*
* @param array $document
*
* @return bool
*/
private function isExpired(array $document)
{
return isset($document[self::EXPIRATION_FIELD]) &&
$document[self::EXPIRATION_FIELD] instanceof MongoDate &&
$document[self::EXPIRATION_FIELD]->sec < time();
return $this->provider->doGetStats();
}
}

View File

@ -1,30 +1,13 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Interface for cache drivers that allows to get many items at once.
*
* @deprecated
*
* @link www.doctrine-project.org
* @since 1.4
* @author Asmir Mustafic <goetas@gmail.com>
*/
interface MultiGetCache
{
@ -32,8 +15,9 @@ interface MultiGetCache
* Returns an associative array of values for keys is found in cache.
*
* @param string[] $keys Array of keys to retrieve from cache
*
* @return mixed[] Array of retrieved values, indexed by the specified keys.
* Values that couldn't be retrieved are not contained in this array.
*/
function fetchMultiple(array $keys);
public function fetchMultiple(array $keys);
}

View File

@ -1,41 +1,24 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Interface for cache drivers that allows to put many items at once.
*
* @deprecated
*
* @link www.doctrine-project.org
* @since 1.6
* @author Daniel Gorgan <danut007ro@gmail.com>
*/
interface MultiPutCache
{
/**
* Returns a boolean value indicating if the operation succeeded.
*
* @param array $keysAndValues Array of keys and values to save in cache
* @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these
* cache entries (0 => infinite lifeTime).
* @param array $keysAndValues Array of keys and values to save in cache
* @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these
* cache entries (0 => infinite lifeTime).
*
* @return bool TRUE if the operation was successful, FALSE if it wasn't.
*/
function saveMultiple(array $keysAndValues, $lifetime = 0);
public function saveMultiple(array $keysAndValues, $lifetime = 0);
}

View File

@ -1,33 +1,29 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use function is_object;
use function method_exists;
use function restore_error_handler;
use function serialize;
use function set_error_handler;
use function sprintf;
use function time;
use function var_export;
/**
* Php file cache driver.
*
* @since 2.3
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class PhpFileCache extends FileCache
{
const EXTENSION = '.doctrinecache.php';
public const EXTENSION = '.doctrinecache.php';
/**
* @var callable
*
* This is cached in a local static variable to avoid instantiating a closure each time we need an empty handler
*/
private static $emptyErrorHandler;
/**
* {@inheritdoc}
@ -35,6 +31,9 @@ class PhpFileCache extends FileCache
public function __construct($directory, $extension = self::EXTENSION, $umask = 0002)
{
parent::__construct($directory, $extension, $umask);
self::$emptyErrorHandler = static function () {
};
}
/**
@ -44,7 +43,7 @@ class PhpFileCache extends FileCache
{
$value = $this->includeFileForId($id);
if (! $value) {
if ($value === null) {
return false;
}
@ -62,7 +61,7 @@ class PhpFileCache extends FileCache
{
$value = $this->includeFileForId($id);
if (! $value) {
if ($value === null) {
return false;
}
@ -78,41 +77,40 @@ class PhpFileCache extends FileCache
$lifeTime = time() + $lifeTime;
}
if (is_object($data) && ! method_exists($data, '__set_state')) {
throw new \InvalidArgumentException(
"Invalid argument given, PhpFileCache only allows objects that implement __set_state() " .
"and fully support var_export(). You can use the FilesystemCache to save arbitrary object " .
"graphs using serialize()/deserialize()."
);
}
$filename = $this->getFilename($id);
$filename = $this->getFilename($id);
$value = array(
$value = [
'lifetime' => $lifeTime,
'data' => $data
);
'data' => $data,
];
$value = var_export($value, true);
$code = sprintf('<?php return %s;', $value);
if (is_object($data) && method_exists($data, '__set_state')) {
$value = var_export($value, true);
$code = sprintf('<?php return %s;', $value);
} else {
$value = var_export(serialize($value), true);
$code = sprintf('<?php return unserialize(%s);', $value);
}
return $this->writeFile($filename, $code);
}
/**
* @param string $id
*
* @return array|false
* @return array|null
*/
private function includeFileForId($id)
private function includeFileForId(string $id) : ?array
{
$fileName = $this->getFilename($id);
// note: error suppression is still faster than `file_exists`, `is_file` and `is_readable`
$value = @include $fileName;
set_error_handler(self::$emptyErrorHandler);
$value = include $fileName;
restore_error_handler();
if (! isset($value['lifetime'])) {
return false;
return null;
}
return $value;

View File

@ -3,24 +3,21 @@
namespace Doctrine\Common\Cache;
use Predis\ClientInterface;
use function array_combine;
use function array_filter;
use function array_map;
use function call_user_func_array;
use function serialize;
use function unserialize;
/**
* Predis cache provider.
*
* @author othillo <othillo@othillo.nl>
*/
class PredisCache extends CacheProvider
{
/**
* @var ClientInterface
*/
/** @var ClientInterface */
private $client;
/**
* @param ClientInterface $client
*
* @return void
*/
public function __construct(ClientInterface $client)
{
$this->client = $client;
@ -32,7 +29,7 @@ class PredisCache extends CacheProvider
protected function doFetch($id)
{
$result = $this->client->get($id);
if (null === $result) {
if ($result === null) {
return false;
}
@ -44,7 +41,7 @@ class PredisCache extends CacheProvider
*/
protected function doFetchMultiple(array $keys)
{
$fetchedItems = call_user_func_array(array($this->client, 'mget'), $keys);
$fetchedItems = call_user_func_array([$this->client, 'mget'], $keys);
return array_map('unserialize', array_filter(array_combine($keys, $fetchedItems)));
}
@ -59,18 +56,20 @@ class PredisCache extends CacheProvider
// Keys have lifetime, use SETEX for each of them
foreach ($keysAndValues as $key => $value) {
$response = $this->client->setex($key, $lifetime, serialize($value));
$response = (string) $this->client->setex($key, $lifetime, serialize($value));
if ((string) $response != 'OK') {
$success = false;
if ($response == 'OK') {
continue;
}
$success = false;
}
return $success;
}
// No lifetime, use MSET
$response = $this->client->mset(array_map(function ($value) {
$response = $this->client->mset(array_map(static function ($value) {
return serialize($value);
}, $keysAndValues));
@ -108,6 +107,14 @@ class PredisCache extends CacheProvider
return $this->client->del($id) >= 0;
}
/**
* {@inheritdoc}
*/
protected function doDeleteMultiple(array $keys)
{
return $this->client->del($keys) >= 0;
}
/**
* {@inheritdoc}
*/
@ -125,12 +132,12 @@ class PredisCache extends CacheProvider
{
$info = $this->client->info();
return array(
return [
Cache::STATS_HITS => $info['Stats']['keyspace_hits'],
Cache::STATS_MISSES => $info['Stats']['keyspace_misses'],
Cache::STATS_UPTIME => $info['Server']['uptime_in_seconds'],
Cache::STATS_MEMORY_USAGE => $info['Memory']['used_memory'],
Cache::STATS_MEMORY_AVAILABLE => false
);
Cache::STATS_MEMORY_AVAILABLE => false,
];
}
}

View File

@ -1,45 +1,31 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use Redis;
use function array_combine;
use function array_diff_key;
use function array_fill_keys;
use function array_filter;
use function array_keys;
use function count;
use function defined;
use function extension_loaded;
use function is_bool;
/**
* Redis cache provider.
*
* @link www.doctrine-project.org
* @since 2.2
* @author Osman Ungur <osmanungur@gmail.com>
*/
class RedisCache extends CacheProvider
{
/**
* @var Redis|null
*/
/** @var Redis|null */
private $redis;
/**
* Sets the redis instance to use.
*
* @param Redis $redis
*
* @return void
*/
public function setRedis(Redis $redis)
@ -74,15 +60,21 @@ class RedisCache extends CacheProvider
$fetchedItems = array_combine($keys, $this->redis->mget($keys));
// Redis mget returns false for keys that do not exist. So we need to filter those out unless it's the real data.
$foundItems = array();
$keysToFilter = array_keys(array_filter($fetchedItems, static function ($item) : bool {
return $item === false;
}));
foreach ($fetchedItems as $key => $value) {
if (false !== $value || $this->redis->exists($key)) {
$foundItems[$key] = $value;
if ($keysToFilter) {
$multi = $this->redis->multi(Redis::PIPELINE);
foreach ($keysToFilter as $key) {
$multi->exists($key);
}
$existItems = array_filter($multi->exec());
$missedItemKeys = array_diff_key($keysToFilter, $existItems);
$fetchedItems = array_diff_key($fetchedItems, array_fill_keys($missedItemKeys, true));
}
return $foundItems;
return $fetchedItems;
}
/**
@ -91,16 +83,14 @@ class RedisCache extends CacheProvider
protected function doSaveMultiple(array $keysAndValues, $lifetime = 0)
{
if ($lifetime) {
$success = true;
// Keys have lifetime, use SETEX for each of them
$multi = $this->redis->multi(Redis::PIPELINE);
foreach ($keysAndValues as $key => $value) {
if (!$this->redis->setex($key, $lifetime, $value)) {
$success = false;
}
$multi->setex($key, $lifetime, $value);
}
$succeeded = array_filter($multi->exec());
return $success;
return count($succeeded) == count($keysAndValues);
}
// No lifetime, use MSET
@ -112,7 +102,13 @@ class RedisCache extends CacheProvider
*/
protected function doContains($id)
{
return $this->redis->exists($id);
$exists = $this->redis->exists($id);
if (is_bool($exists)) {
return $exists;
}
return $exists > 0;
}
/**
@ -132,7 +128,15 @@ class RedisCache extends CacheProvider
*/
protected function doDelete($id)
{
return $this->redis->delete($id) >= 0;
return $this->redis->del($id) >= 0;
}
/**
* {@inheritdoc}
*/
protected function doDeleteMultiple(array $keys)
{
return $this->redis->del($keys) >= 0;
}
/**
@ -149,13 +153,14 @@ class RedisCache extends CacheProvider
protected function doGetStats()
{
$info = $this->redis->info();
return array(
return [
Cache::STATS_HITS => $info['keyspace_hits'],
Cache::STATS_MISSES => $info['keyspace_misses'],
Cache::STATS_UPTIME => $info['uptime_in_seconds'],
Cache::STATS_MEMORY_USAGE => $info['used_memory'],
Cache::STATS_MEMORY_AVAILABLE => false
);
Cache::STATS_MEMORY_AVAILABLE => false,
];
}
/**
@ -163,14 +168,10 @@ class RedisCache extends CacheProvider
* igbinary support, that is used. Otherwise the default PHP serializer is
* used.
*
* @return integer One of the Redis::SERIALIZER_* constants
* @return int One of the Redis::SERIALIZER_* constants
*/
protected function getSerializerValue()
{
if (defined('HHVM_VERSION')) {
return Redis::SERIALIZER_PHP;
}
if (defined('Redis::SERIALIZER_IGBINARY') && extension_loaded('igbinary')) {
return Redis::SERIALIZER_IGBINARY;
}

View File

@ -1,68 +1,50 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use SQLite3;
use SQLite3Result;
use const SQLITE3_ASSOC;
use const SQLITE3_BLOB;
use const SQLITE3_TEXT;
use function array_search;
use function implode;
use function serialize;
use function sprintf;
use function time;
use function unserialize;
/**
* SQLite3 cache provider.
*
* @since 1.4
* @author Jake Bell <jake@theunraveler.com>
*/
class SQLite3Cache extends CacheProvider
{
/**
* The ID field will store the cache key.
*/
const ID_FIELD = 'k';
public const ID_FIELD = 'k';
/**
* The data field will store the serialized PHP value.
*/
const DATA_FIELD = 'd';
public const DATA_FIELD = 'd';
/**
* The expiration field will store a date value indicating when the
* cache entry should expire.
*/
const EXPIRATION_FIELD = 'e';
public const EXPIRATION_FIELD = 'e';
/**
* @var SQLite3
*/
/** @var SQLite3 */
private $sqlite;
/**
* @var string
*/
/** @var string */
private $table;
/**
* Constructor.
*
* Calling the constructor will ensure that the database file and table
* Calling the constructor will ensure that the database file and table
* exist and will create both if they don't.
*
* @param SQLite3 $sqlite
* @param string $table
*/
public function __construct(SQLite3 $sqlite, $table)
@ -70,15 +52,20 @@ class SQLite3Cache extends CacheProvider
$this->sqlite = $sqlite;
$this->table = (string) $table;
list($id, $data, $exp) = $this->getFields();
$this->ensureTableExists();
}
return $this->sqlite->exec(sprintf(
'CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY NOT NULL, %s BLOB, %s INTEGER)',
$table,
$id,
$data,
$exp
));
private function ensureTableExists() : void
{
$this->sqlite->exec(
sprintf(
'CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY NOT NULL, %s BLOB, %s INTEGER)',
$this->table,
static::ID_FIELD,
static::DATA_FIELD,
static::EXPIRATION_FIELD
)
);
}
/**
@ -86,11 +73,13 @@ class SQLite3Cache extends CacheProvider
*/
protected function doFetch($id)
{
if ($item = $this->findById($id)) {
return unserialize($item[self::DATA_FIELD]);
$item = $this->findById($id);
if (! $item) {
return false;
}
return false;
return unserialize($item[self::DATA_FIELD]);
}
/**
@ -98,7 +87,7 @@ class SQLite3Cache extends CacheProvider
*/
protected function doContains($id)
{
return null !== $this->findById($id, false);
return $this->findById($id, false) !== null;
}
/**
@ -124,7 +113,7 @@ class SQLite3Cache extends CacheProvider
*/
protected function doDelete($id)
{
list($idField) = $this->getFields();
[$idField] = $this->getFields();
$statement = $this->sqlite->prepare(sprintf(
'DELETE FROM %s WHERE %s = :id',
@ -157,15 +146,14 @@ class SQLite3Cache extends CacheProvider
* Find a single row by ID.
*
* @param mixed $id
* @param bool $includeData
*
* @return array|null
*/
private function findById($id, $includeData = true)
private function findById($id, bool $includeData = true) : ?array
{
list($idField) = $fields = $this->getFields();
[$idField] = $fields = $this->getFields();
if (!$includeData) {
if (! $includeData) {
$key = array_search(static::DATA_FIELD, $fields);
unset($fields[$key]);
}
@ -199,19 +187,17 @@ class SQLite3Cache extends CacheProvider
*
* @return array
*/
private function getFields()
private function getFields() : array
{
return array(static::ID_FIELD, static::DATA_FIELD, static::EXPIRATION_FIELD);
return [static::ID_FIELD, static::DATA_FIELD, static::EXPIRATION_FIELD];
}
/**
* Check if the item is expired.
*
* @param array $item
*
* @return bool
*/
private function isExpired(array $item)
private function isExpired(array $item) : bool
{
return isset($item[static::EXPIRATION_FIELD]) &&
$item[self::EXPIRATION_FIELD] !== null &&

View File

@ -1,25 +1,8 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
class Version
{
const VERSION = '1.6.1-DEV';
public const VERSION = '1.9.0-DEV';
}

View File

@ -1,21 +1,4 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
@ -23,8 +6,6 @@ namespace Doctrine\Common\Cache;
* Void cache driver. The cache could be of use in tests where you don`t need to cache anything.
*
* @link www.doctrine-project.org
* @since 1.5
* @author Kotlyar Maksim <kotlyar.maksim@gmail.com>
*/
class VoidCache extends CacheProvider
{

View File

@ -1,34 +1,21 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use function count;
use function is_array;
use function wincache_ucache_clear;
use function wincache_ucache_delete;
use function wincache_ucache_exists;
use function wincache_ucache_get;
use function wincache_ucache_info;
use function wincache_ucache_meminfo;
use function wincache_ucache_set;
/**
* WinCache cache provider.
*
* @link www.doctrine-project.org
* @since 2.2
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class WinCacheCache extends CacheProvider
{
@ -90,6 +77,16 @@ class WinCacheCache extends CacheProvider
return empty($result);
}
/**
* {@inheritdoc}
*/
protected function doDeleteMultiple(array $keys)
{
$result = wincache_ucache_delete($keys);
return is_array($result) && count($result) !== count($keys);
}
/**
* {@inheritdoc}
*/
@ -98,12 +95,12 @@ class WinCacheCache extends CacheProvider
$info = wincache_ucache_info();
$meminfo = wincache_ucache_meminfo();
return array(
return [
Cache::STATS_HITS => $info['total_hit_count'],
Cache::STATS_MISSES => $info['total_miss_count'],
Cache::STATS_UPTIME => $info['total_cache_uptime'],
Cache::STATS_MEMORY_USAGE => $meminfo['memory_total'],
Cache::STATS_MEMORY_AVAILABLE => $meminfo['memory_free'],
);
];
}
}

View File

@ -1,34 +1,25 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use BadMethodCallException;
use const XC_TYPE_VAR;
use function ini_get;
use function serialize;
use function unserialize;
use function xcache_clear_cache;
use function xcache_get;
use function xcache_info;
use function xcache_isset;
use function xcache_set;
use function xcache_unset;
/**
* Xcache cache driver.
*
* @deprecated
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class XcacheCache extends CacheProvider
{
@ -81,12 +72,12 @@ class XcacheCache extends CacheProvider
*
* @return void
*
* @throws \BadMethodCallException When xcache.admin.enable_auth is On.
* @throws BadMethodCallException When xcache.admin.enable_auth is On.
*/
protected function checkAuthorization()
{
if (ini_get('xcache.admin.enable_auth')) {
throw new \BadMethodCallException(
throw new BadMethodCallException(
'To use all features of \Doctrine\Common\Cache\XcacheCache, '
. 'you must set "xcache.admin.enable_auth" to "Off" in your php.ini.'
);
@ -101,12 +92,13 @@ class XcacheCache extends CacheProvider
$this->checkAuthorization();
$info = xcache_info(XC_TYPE_VAR, 0);
return array(
return [
Cache::STATS_HITS => $info['hits'],
Cache::STATS_MISSES => $info['misses'],
Cache::STATS_UPTIME => null,
Cache::STATS_MEMORY_USAGE => $info['size'],
Cache::STATS_MEMORY_AVAILABLE => $info['avail'],
);
];
}
}

View File

@ -1,31 +1,16 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use function zend_shm_cache_clear;
use function zend_shm_cache_delete;
use function zend_shm_cache_fetch;
use function zend_shm_cache_store;
/**
* Zend Data Cache cache driver.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Ralph Schindler <ralph.schindler@zend.com>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
*/
class ZendDataCache extends CacheProvider
{
@ -42,7 +27,7 @@ class ZendDataCache extends CacheProvider
*/
protected function doContains($id)
{
return (false !== zend_shm_cache_fetch($id));
return zend_shm_cache_fetch($id) !== false;
}
/**
@ -70,6 +55,7 @@ class ZendDataCache extends CacheProvider
if (empty($namespace)) {
return zend_shm_cache_clear();
}
return zend_shm_cache_clear($namespace);
}

View File

@ -18,20 +18,7 @@ branches.
## Coding Standard
We use [doctrine coding standard](https://github.com/doctrine/coding-standard) which is PSR-1 and PSR-2:
* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md
* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
with some exceptions/differences:
* Keep the nesting of control structures per method as small as possible
* Align equals (=) signs
* Add spaces between assignment, control and return statements
* Prefer early exit over nesting conditions
* Add spaces around a negation if condition ``if ( ! $cond)``
* Add legal information at the beginning of each source file
* Add ``@author`` [phpDoc](https://www.phpdoc.org/docs/latest/references/phpdoc/tags/author.html) comment at DockBlock of class/interface/trait that you create.
We use the [Doctrine Coding Standard](https://github.com/doctrine/coding-standard).
## Unit-Tests

View File

@ -1,13 +1,78 @@
# Doctrine Collections
[![Build Status](https://travis-ci.org/doctrine/collections.svg?branch=master)](https://travis-ci.org/doctrine/collections)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/doctrine/collections/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/doctrine/collections/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/doctrine/collections/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/doctrine/collections/?branch=master)
[![Code Coverage](https://codecov.io/gh/doctrine/collections/branch/master/graph/badge.svg)](https://codecov.io/gh/doctrine/collections/branch/master)
Collections Abstraction library
## Changelog
### v1.6.1
This release, combined with the release of [`doctrine/collections` `v1.6.1`](https://github.com/doctrine/collections/releases/tag/v1.6.1),
fixes an issue where parsing annotations was not possible
for classes within `doctrine/collections`.
Specifically, `v1.6.0` introduced Psalm-specific annotations
such as (for example) `@template` and `@template-implements`,
which were both incorrectly recognized as `@template`.
`@template` has therefore been removed, and instead we use
the prefixed `@psalm-template`, which is no longer parsed
by `doctrine/collections` `v1.6.1`
Total issues resolved: **1**
- [186: Use `@psalm-template` annotation to avoid clashes](https://github.com/doctrine/collections/pull/186) thanks to @muglug
### v1.6.0
This release bumps the minimum required PHP version to 7.1.3.
Following improvements were introduced:
* `ArrayCollection#filter()` now allows filtering by key, value or both.
* When using the `ClosureExpressionVisitor` over objects with a defined
accessor and property, the accessor is prioritised.
* Updated testing tools and coding standards, autoloading, which also
led to marginal performance improvements
* Introduced generic type docblock declarations from [psalm](https://github.com/vimeo/psalm),
which should allow users to declare `/** @var Collection<KeyType, ValueType> */`
in their code, and leverage the type propagation deriving from that.
Total issues resolved: **16**
- [127: Use PSR-4](https://github.com/doctrine/collections/pull/127) thanks to @Nyholm
- [129: Remove space in method declaration](https://github.com/doctrine/collections/pull/129) thanks to @bounoable
- [130: Update build to add PHPCS and PHPStan](https://github.com/doctrine/collections/pull/130) thanks to @lcobucci
- [131: ClosureExpressionVisitor &gt; Don't duplicate the accessor when the field already starts with it](https://github.com/doctrine/collections/pull/131) thanks to @ruudk
- [139: Apply Doctrine CS 2.1](https://github.com/doctrine/collections/pull/139) thanks to @Majkl578
- [142: CS 4.0, version composer.lock, merge stages](https://github.com/doctrine/collections/pull/142) thanks to @Majkl578
- [144: Update to PHPUnit 7](https://github.com/doctrine/collections/pull/144) thanks to @carusogabriel
- [146: Update changelog for v1.4.0 and v1.5.0](https://github.com/doctrine/collections/pull/146) thanks to @GromNaN
- [154: Update index.rst](https://github.com/doctrine/collections/pull/154) thanks to @chraiet
- [158: Extract Selectable method into own documentation section](https://github.com/doctrine/collections/pull/158) thanks to @SenseException
- [160: Update homepage](https://github.com/doctrine/collections/pull/160) thanks to @Majkl578
- [165: Allow `ArrayCollection#filter()` to filter by key, value or both](https://github.com/doctrine/collections/issues/165) thanks to @0x13a
- [167: Allow `ArrayCollection#filter()` to filter by key and also value](https://github.com/doctrine/collections/pull/167) thanks to @0x13a
- [175: CI: Test against PHP 7.4snapshot instead of nightly (8.0)](https://github.com/doctrine/collections/pull/175) thanks to @Majkl578
- [177: Generify collections using Psalm](https://github.com/doctrine/collections/pull/177) thanks to @nschoellhorn
- [178: Updated doctrine/coding-standard to 6.0](https://github.com/doctrine/collections/pull/178) thanks to @patrickjahns
### v1.5.0
* [Require PHP 7.1+](https://github.com/doctrine/collections/pull/105)
* [Drop HHVM support](https://github.com/doctrine/collections/pull/118)
### v1.4.0
* [Require PHP 5.6+](https://github.com/doctrine/collections/pull/105)
* [Add `ArrayCollection::createFrom()`](https://github.com/doctrine/collections/pull/91)
* [Support non-camel-case naming](https://github.com/doctrine/collections/pull/57)
* [Comparison `START_WITH`, `END_WITH`](https://github.com/doctrine/collections/pull/78)
* [Comparison `MEMBER_OF`](https://github.com/doctrine/collections/pull/66)
* [Add Contributing guide](https://github.com/doctrine/collections/pull/103)
### v1.3.0
* [Explicit casting of first and max results in criteria API](https://github.com/doctrine/collections/pull/26)

View File

@ -1,9 +1,14 @@
{
"name": "doctrine/collections",
"type": "library",
"description": "Collections Abstraction library",
"keywords": ["collections", "array", "iterator"],
"homepage": "http://www.doctrine-project.org",
"description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.",
"keywords": [
"php",
"collections",
"array",
"iterators"
],
"homepage": "https://www.doctrine-project.org/projects/collections.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
@ -13,23 +18,20 @@
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
],
"require": {
"php": "^5.6 || ^7.0"
"php": "^7.1.3 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^5.7",
"doctrine/coding-standard": "~0.1@dev"
"phpunit/phpunit": "^7.0",
"doctrine/coding-standard": "^6.0",
"phpstan/phpstan-shim": "^0.9.2",
"vimeo/psalm": "^3.8.1"
},
"autoload": {
"psr-0": { "Doctrine\\Common\\Collections\\": "lib/" }
"psr-4": { "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" }
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\": "tests/Doctrine/Tests"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.3.x-dev"
}
}
}

View File

@ -1,21 +1,4 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections;
@ -24,21 +7,22 @@ use Closure;
/**
* Lazy collection that is backed by a concrete collection
*
* @author Michaël Gallego <mic.gallego@gmail.com>
* @since 1.2
* @phpstan-template TKey
* @psalm-template TKey of array-key
* @psalm-template T
* @template-implements Collection<TKey,T>
*/
abstract class AbstractLazyCollection implements Collection
{
/**
* The backed collection to use
*
* @psalm-var Collection<TKey,T>
* @var Collection
*/
protected $collection;
/**
* @var boolean
*/
/** @var bool */
protected $initialized = false;
/**
@ -47,6 +31,7 @@ abstract class AbstractLazyCollection implements Collection
public function count()
{
$this->initialize();
return $this->collection->count();
}
@ -56,6 +41,7 @@ abstract class AbstractLazyCollection implements Collection
public function add($element)
{
$this->initialize();
return $this->collection->add($element);
}
@ -74,6 +60,7 @@ abstract class AbstractLazyCollection implements Collection
public function contains($element)
{
$this->initialize();
return $this->collection->contains($element);
}
@ -83,6 +70,7 @@ abstract class AbstractLazyCollection implements Collection
public function isEmpty()
{
$this->initialize();
return $this->collection->isEmpty();
}
@ -92,6 +80,7 @@ abstract class AbstractLazyCollection implements Collection
public function remove($key)
{
$this->initialize();
return $this->collection->remove($key);
}
@ -101,6 +90,7 @@ abstract class AbstractLazyCollection implements Collection
public function removeElement($element)
{
$this->initialize();
return $this->collection->removeElement($element);
}
@ -110,6 +100,7 @@ abstract class AbstractLazyCollection implements Collection
public function containsKey($key)
{
$this->initialize();
return $this->collection->containsKey($key);
}
@ -119,6 +110,7 @@ abstract class AbstractLazyCollection implements Collection
public function get($key)
{
$this->initialize();
return $this->collection->get($key);
}
@ -128,6 +120,7 @@ abstract class AbstractLazyCollection implements Collection
public function getKeys()
{
$this->initialize();
return $this->collection->getKeys();
}
@ -137,6 +130,7 @@ abstract class AbstractLazyCollection implements Collection
public function getValues()
{
$this->initialize();
return $this->collection->getValues();
}
@ -155,6 +149,7 @@ abstract class AbstractLazyCollection implements Collection
public function toArray()
{
$this->initialize();
return $this->collection->toArray();
}
@ -164,6 +159,7 @@ abstract class AbstractLazyCollection implements Collection
public function first()
{
$this->initialize();
return $this->collection->first();
}
@ -173,6 +169,7 @@ abstract class AbstractLazyCollection implements Collection
public function last()
{
$this->initialize();
return $this->collection->last();
}
@ -182,6 +179,7 @@ abstract class AbstractLazyCollection implements Collection
public function key()
{
$this->initialize();
return $this->collection->key();
}
@ -191,6 +189,7 @@ abstract class AbstractLazyCollection implements Collection
public function current()
{
$this->initialize();
return $this->collection->current();
}
@ -200,6 +199,7 @@ abstract class AbstractLazyCollection implements Collection
public function next()
{
$this->initialize();
return $this->collection->next();
}
@ -209,6 +209,7 @@ abstract class AbstractLazyCollection implements Collection
public function exists(Closure $p)
{
$this->initialize();
return $this->collection->exists($p);
}
@ -218,6 +219,7 @@ abstract class AbstractLazyCollection implements Collection
public function filter(Closure $p)
{
$this->initialize();
return $this->collection->filter($p);
}
@ -227,6 +229,7 @@ abstract class AbstractLazyCollection implements Collection
public function forAll(Closure $p)
{
$this->initialize();
return $this->collection->forAll($p);
}
@ -236,6 +239,7 @@ abstract class AbstractLazyCollection implements Collection
public function map(Closure $func)
{
$this->initialize();
return $this->collection->map($func);
}
@ -245,6 +249,7 @@ abstract class AbstractLazyCollection implements Collection
public function partition(Closure $p)
{
$this->initialize();
return $this->collection->partition($p);
}
@ -254,6 +259,7 @@ abstract class AbstractLazyCollection implements Collection
public function indexOf($element)
{
$this->initialize();
return $this->collection->indexOf($element);
}
@ -263,6 +269,7 @@ abstract class AbstractLazyCollection implements Collection
public function slice($offset, $length = null)
{
$this->initialize();
return $this->collection->slice($offset, $length);
}
@ -272,29 +279,44 @@ abstract class AbstractLazyCollection implements Collection
public function getIterator()
{
$this->initialize();
return $this->collection->getIterator();
}
/**
* {@inheritDoc}
*
* @psalm-param TKey $offset
*/
public function offsetExists($offset)
{
$this->initialize();
return $this->collection->offsetExists($offset);
}
/**
* {@inheritDoc}
*
* @param int|string $offset
*
* @return mixed
*
* @psalm-param TKey $offset
*/
public function offsetGet($offset)
{
$this->initialize();
return $this->collection->offsetGet($offset);
}
/**
* {@inheritDoc}
*
* @param mixed $value
*
* @psalm-param TKey $offset
*/
public function offsetSet($offset, $value)
{
@ -304,6 +326,8 @@ abstract class AbstractLazyCollection implements Collection
/**
* {@inheritDoc}
*
* @psalm-param TKey $offset
*/
public function offsetUnset($offset)
{
@ -328,10 +352,12 @@ abstract class AbstractLazyCollection implements Collection
*/
protected function initialize()
{
if ( ! $this->initialized) {
$this->doInitialize();
$this->initialized = true;
if ($this->initialized) {
return;
}
$this->doInitialize();
$this->initialized = true;
}
/**

View File

@ -1,27 +1,28 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections;
use ArrayIterator;
use Closure;
use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
use const ARRAY_FILTER_USE_BOTH;
use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_map;
use function array_reverse;
use function array_search;
use function array_slice;
use function array_values;
use function count;
use function current;
use function end;
use function in_array;
use function key;
use function next;
use function reset;
use function spl_object_hash;
use function uasort;
/**
* An ArrayCollection is a Collection implementation that wraps a regular PHP array.
@ -31,16 +32,18 @@ use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
* serialize a collection use {@link toArray()} and reconstruct the collection
* manually.
*
* @since 2.0
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @phpstan-template TKey
* @psalm-template TKey of array-key
* @psalm-template T
* @template-implements Collection<TKey,T>
* @template-implements Selectable<TKey,T>
*/
class ArrayCollection implements Collection, Selectable
{
/**
* An array containing the entries of this collection.
*
* @psalm-var array<TKey,T>
* @var array
*/
private $elements;
@ -49,27 +52,14 @@ class ArrayCollection implements Collection, Selectable
* Initializes a new ArrayCollection.
*
* @param array $elements
*
* @psalm-param array<TKey,T> $elements
*/
public function __construct(array $elements = array())
public function __construct(array $elements = [])
{
$this->elements = $elements;
}
/**
* Creates a new instance from the specified elements.
*
* This method is provided for derived classes to specify how a new
* instance should be created when constructor semantics have changed.
*
* @param array $elements Elements.
*
* @return static
*/
protected function createFrom(array $elements)
{
return new static($elements);
}
/**
* {@inheritDoc}
*/
@ -86,6 +76,24 @@ class ArrayCollection implements Collection, Selectable
return reset($this->elements);
}
/**
* Creates a new instance from the specified elements.
*
* This method is provided for derived classes to specify how a new
* instance should be created when constructor semantics have changed.
*
* @param array $elements Elements.
*
* @return static
*
* @psalm-param array<TKey,T> $elements
* @psalm-return static<TKey,T>
*/
protected function createFrom(array $elements)
{
return new static($elements);
}
/**
* {@inheritDoc}
*/
@ -123,7 +131,7 @@ class ArrayCollection implements Collection, Selectable
*/
public function remove($key)
{
if ( ! isset($this->elements[$key]) && ! array_key_exists($key, $this->elements)) {
if (! isset($this->elements[$key]) && ! array_key_exists($key, $this->elements)) {
return null;
}
@ -153,6 +161,8 @@ class ArrayCollection implements Collection, Selectable
* Required by interface ArrayAccess.
*
* {@inheritDoc}
*
* @psalm-param TKey $offset
*/
public function offsetExists($offset)
{
@ -163,6 +173,8 @@ class ArrayCollection implements Collection, Selectable
* Required by interface ArrayAccess.
*
* {@inheritDoc}
*
* @psalm-param TKey $offset
*/
public function offsetGet($offset)
{
@ -176,8 +188,10 @@ class ArrayCollection implements Collection, Selectable
*/
public function offsetSet($offset, $value)
{
if ( ! isset($offset)) {
return $this->add($value);
if (! isset($offset)) {
$this->add($value);
return;
}
$this->set($offset, $value);
@ -187,10 +201,12 @@ class ArrayCollection implements Collection, Selectable
* Required by interface ArrayAccess.
*
* {@inheritDoc}
*
* @psalm-param TKey $offset
*/
public function offsetUnset($offset)
{
return $this->remove($offset);
$this->remove($offset);
}
/**
@ -236,7 +252,7 @@ class ArrayCollection implements Collection, Selectable
*/
public function get($key)
{
return isset($this->elements[$key]) ? $this->elements[$key] : null;
return $this->elements[$key] ?? null;
}
/**
@ -273,6 +289,11 @@ class ArrayCollection implements Collection, Selectable
/**
* {@inheritDoc}
*
* @psalm-suppress InvalidPropertyAssignmentValue
*
* This breaks assumptions about the template type, but it would
* be a backwards-incompatible change to remove this method
*/
public function add($element)
{
@ -301,6 +322,12 @@ class ArrayCollection implements Collection, Selectable
/**
* {@inheritDoc}
*
* @return static
*
* @psalm-template U
* @psalm-param Closure(T=):U $func
* @psalm-return static<TKey, U>
*/
public function map(Closure $func)
{
@ -309,10 +336,14 @@ class ArrayCollection implements Collection, Selectable
/**
* {@inheritDoc}
*
* @return static
*
* @psalm-return static<TKey,T>
*/
public function filter(Closure $p)
{
return $this->createFrom(array_filter($this->elements, $p));
return $this->createFrom(array_filter($this->elements, $p, ARRAY_FILTER_USE_BOTH));
}
/**
@ -321,7 +352,7 @@ class ArrayCollection implements Collection, Selectable
public function forAll(Closure $p)
{
foreach ($this->elements as $key => $element) {
if ( ! $p($key, $element)) {
if (! $p($key, $element)) {
return false;
}
}
@ -334,7 +365,7 @@ class ArrayCollection implements Collection, Selectable
*/
public function partition(Closure $p)
{
$matches = $noMatches = array();
$matches = $noMatches = [];
foreach ($this->elements as $key => $element) {
if ($p($key, $element)) {
@ -344,7 +375,7 @@ class ArrayCollection implements Collection, Selectable
}
}
return array($this->createFrom($matches), $this->createFrom($noMatches));
return [$this->createFrom($matches), $this->createFrom($noMatches)];
}
/**
@ -354,7 +385,7 @@ class ArrayCollection implements Collection, Selectable
*/
public function __toString()
{
return __CLASS__ . '@' . spl_object_hash($this);
return self::class . '@' . spl_object_hash($this);
}
/**
@ -362,7 +393,7 @@ class ArrayCollection implements Collection, Selectable
*/
public function clear()
{
$this->elements = array();
$this->elements = [];
}
/**
@ -387,10 +418,12 @@ class ArrayCollection implements Collection, Selectable
$filtered = array_filter($filtered, $filter);
}
if ($orderings = $criteria->getOrderings()) {
$orderings = $criteria->getOrderings();
if ($orderings) {
$next = null;
foreach (array_reverse($orderings) as $field => $ordering) {
$next = ClosureExpressionVisitor::sortByField($field, $ordering == Criteria::DESC ? -1 : 1, $next);
$next = ClosureExpressionVisitor::sortByField($field, $ordering === Criteria::DESC ? -1 : 1, $next);
}
uasort($filtered, $next);
@ -400,7 +433,7 @@ class ArrayCollection implements Collection, Selectable
$length = $criteria->getMaxResults();
if ($offset || $length) {
$filtered = array_slice($filtered, (int)$offset, $length);
$filtered = array_slice($filtered, (int) $offset, $length);
}
return $this->createFrom($filtered);

View File

@ -1,21 +1,4 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections;
@ -41,10 +24,11 @@ use IteratorAggregate;
* position unless you explicitly positioned it before. Prefer iteration with
* external iterators.
*
* @since 2.0
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @phpstan-template TKey
* @psalm-template TKey of array-key
* @psalm-template T
* @template-extends IteratorAggregate<TKey, T>
* @template-extends ArrayAccess<TKey|null, T>
*/
interface Collection extends Countable, IteratorAggregate, ArrayAccess
{
@ -53,7 +37,9 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
*
* @param mixed $element The element to add.
*
* @return boolean Always TRUE.
* @return true Always TRUE.
*
* @psalm-param T $element
*/
public function add($element);
@ -70,23 +56,28 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
*
* @param mixed $element The element to search for.
*
* @return boolean TRUE if the collection contains the element, FALSE otherwise.
* @return bool TRUE if the collection contains the element, FALSE otherwise.
*
* @psalm-param T $element
*/
public function contains($element);
/**
* Checks whether the collection is empty (contains no elements).
*
* @return boolean TRUE if the collection is empty, FALSE otherwise.
* @return bool TRUE if the collection is empty, FALSE otherwise.
*/
public function isEmpty();
/**
* Removes the element at the specified index from the collection.
*
* @param string|integer $key The kex/index of the element to remove.
* @param string|int $key The key/index of the element to remove.
*
* @return mixed The removed element or NULL, if the collection did not contain the element.
*
* @psalm-param TKey $key
* @psalm-return T|null
*/
public function remove($key);
@ -95,34 +86,43 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
*
* @param mixed $element The element to remove.
*
* @return boolean TRUE if this collection contained the specified element, FALSE otherwise.
* @return bool TRUE if this collection contained the specified element, FALSE otherwise.
*
* @psalm-param T $element
*/
public function removeElement($element);
/**
* Checks whether the collection contains an element with the specified key/index.
*
* @param string|integer $key The key/index to check for.
* @param string|int $key The key/index to check for.
*
* @return boolean TRUE if the collection contains an element with the specified key/index,
* FALSE otherwise.
* @return bool TRUE if the collection contains an element with the specified key/index,
* FALSE otherwise.
*
* @psalm-param TKey $key
*/
public function containsKey($key);
/**
* Gets the element at the specified key/index.
*
* @param string|integer $key The key/index of the element to retrieve.
* @param string|int $key The key/index of the element to retrieve.
*
* @return mixed
*
* @psalm-param TKey $key
* @psalm-return T|null
*/
public function get($key);
/**
* Gets all keys/indices of the collection.
*
* @return array The keys/indices of the collection, in the order of the corresponding
* @return int[]|string[] The keys/indices of the collection, in the order of the corresponding
* elements in the collection.
*
* @psalm-return TKey[]
*/
public function getKeys();
@ -131,16 +131,21 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
*
* @return array The values of all elements in the collection, in the order they
* appear in the collection.
*
* @psalm-return T[]
*/
public function getValues();
/**
* Sets an element in the collection at the specified key/index.
*
* @param string|integer $key The key/index of the element to set.
* @param mixed $value The element to set.
* @param string|int $key The key/index of the element to set.
* @param mixed $value The element to set.
*
* @return void
*
* @psalm-param TKey $key
* @psalm-param T $value
*/
public function set($key, $value);
@ -148,6 +153,8 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
* Gets a native PHP array representation of the collection.
*
* @return array
*
* @psalm-return array<TKey,T>
*/
public function toArray();
@ -155,6 +162,8 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
* Sets the internal iterator to the first element in the collection and returns this element.
*
* @return mixed
*
* @psalm-return T|false
*/
public function first();
@ -162,13 +171,17 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
* Sets the internal iterator to the last element in the collection and returns this element.
*
* @return mixed
*
* @psalm-return T|false
*/
public function last();
/**
* Gets the key/index of the element at the current iterator position.
*
* @return int|string
* @return int|string|null
*
* @psalm-return TKey|null
*/
public function key();
@ -176,6 +189,8 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
* Gets the element of the collection at the current iterator position.
*
* @return mixed
*
* @psalm-return T|false
*/
public function current();
@ -183,6 +198,8 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
* Moves the internal iterator position to the next element and returns this element.
*
* @return mixed
*
* @psalm-return T|false
*/
public function next();
@ -191,7 +208,9 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
*
* @param Closure $p The predicate.
*
* @return boolean TRUE if the predicate is TRUE for at least one element, FALSE otherwise.
* @return bool TRUE if the predicate is TRUE for at least one element, FALSE otherwise.
*
* @psalm-param Closure(TKey=, T=):bool $p
*/
public function exists(Closure $p);
@ -202,6 +221,9 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
* @param Closure $p The predicate used for filtering.
*
* @return Collection A collection with the results of the filter operation.
*
* @psalm-param Closure(T=):bool $p
* @psalm-return Collection<TKey, T>
*/
public function filter(Closure $p);
@ -210,7 +232,9 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
*
* @param Closure $p The predicate.
*
* @return boolean TRUE, if the predicate yields TRUE for all elements, FALSE otherwise.
* @return bool TRUE, if the predicate yields TRUE for all elements, FALSE otherwise.
*
* @psalm-param Closure(TKey=, T=):bool $p
*/
public function forAll(Closure $p);
@ -218,9 +242,11 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
* Applies the given function to each element in the collection and returns
* a new collection with the elements returned by the function.
*
* @param Closure $func
*
* @return Collection
*
* @psalm-template U
* @psalm-param Closure(T=):U $func
* @psalm-return Collection<TKey, U>
*/
public function map(Closure $func);
@ -230,9 +256,12 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
*
* @param Closure $p The predicate on which to partition.
*
* @return array An array with two elements. The first element contains the collection
* of elements where the predicate returned TRUE, the second element
* contains the collection of elements where the predicate returned FALSE.
* @return Collection[] An array with two elements. The first element contains the collection
* of elements where the predicate returned TRUE, the second element
* contains the collection of elements where the predicate returned FALSE.
*
* @psalm-param Closure(TKey=, T=):bool $p
* @psalm-return array{0: Collection<TKey, T>, 1: Collection<TKey, T>}
*/
public function partition(Closure $p);
@ -244,6 +273,9 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
* @param mixed $element The element to search for.
*
* @return int|string|bool The key/index of the element or FALSE if the element was not found.
*
* @psalm-param T $element
* @psalm-return TKey|false
*/
public function indexOf($element);
@ -258,6 +290,8 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
* @param int|null $length The maximum number of elements to return, or null for no limit.
*
* @return array
*
* @psalm-return array<TKey,T>
*/
public function slice($offset, $length = null);
}

View File

@ -1,68 +1,34 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections;
use Doctrine\Common\Collections\Expr\Expression;
use Doctrine\Common\Collections\Expr\CompositeExpression;
use Doctrine\Common\Collections\Expr\Expression;
use function array_map;
use function strtoupper;
/**
* Criteria for filtering Selectable collections.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.3
*/
class Criteria
{
/**
* @var string
*/
const ASC = 'ASC';
public const ASC = 'ASC';
/**
* @var string
*/
const DESC = 'DESC';
public const DESC = 'DESC';
/**
* @var \Doctrine\Common\Collections\ExpressionBuilder|null
*/
/** @var ExpressionBuilder|null */
private static $expressionBuilder;
/**
* @var \Doctrine\Common\Collections\Expr\Expression|null
*/
/** @var Expression|null */
private $expression;
/**
* @var string[]
*/
private $orderings = array();
/** @var string[] */
private $orderings = [];
/**
* @var int|null
*/
/** @var int|null */
private $firstResult;
/**
* @var int|null
*/
/** @var int|null */
private $maxResults;
/**
@ -78,7 +44,7 @@ class Criteria
/**
* Returns the expression builder.
*
* @return \Doctrine\Common\Collections\ExpressionBuilder
* @return ExpressionBuilder
*/
public static function expr()
{
@ -92,28 +58,27 @@ class Criteria
/**
* Construct a new Criteria.
*
* @param Expression $expression
* @param string[]|null $orderings
* @param int|null $firstResult
* @param int|null $maxResults
*/
public function __construct(Expression $expression = null, array $orderings = null, $firstResult = null, $maxResults = null)
public function __construct(?Expression $expression = null, ?array $orderings = null, $firstResult = null, $maxResults = null)
{
$this->expression = $expression;
$this->setFirstResult($firstResult);
$this->setMaxResults($maxResults);
if (null !== $orderings) {
$this->orderBy($orderings);
if ($orderings === null) {
return;
}
$this->orderBy($orderings);
}
/**
* Sets the where expression to evaluate when this Criteria is searched for.
*
* @param Expression $expression
*
* @return Criteria
*/
public function where(Expression $expression)
@ -127,8 +92,6 @@ class Criteria
* Appends the where expression to evaluate when this Criteria is searched for
* using an AND with previous expression.
*
* @param Expression $expression
*
* @return Criteria
*/
public function andWhere(Expression $expression)
@ -137,9 +100,10 @@ class Criteria
return $this->where($expression);
}
$this->expression = new CompositeExpression(CompositeExpression::TYPE_AND, array(
$this->expression, $expression
));
$this->expression = new CompositeExpression(
CompositeExpression::TYPE_AND,
[$this->expression, $expression]
);
return $this;
}
@ -148,8 +112,6 @@ class Criteria
* Appends the where expression to evaluate when this Criteria is searched for
* using an OR with previous expression.
*
* @param Expression $expression
*
* @return Criteria
*/
public function orWhere(Expression $expression)
@ -158,9 +120,10 @@ class Criteria
return $this->where($expression);
}
$this->expression = new CompositeExpression(CompositeExpression::TYPE_OR, array(
$this->expression, $expression
));
$this->expression = new CompositeExpression(
CompositeExpression::TYPE_OR,
[$this->expression, $expression]
);
return $this;
}
@ -200,7 +163,7 @@ class Criteria
public function orderBy(array $orderings)
{
$this->orderings = array_map(
function ($ordering) {
static function (string $ordering) : string {
return strtoupper($ordering) === Criteria::ASC ? Criteria::ASC : Criteria::DESC;
},
$orderings
@ -228,7 +191,7 @@ class Criteria
*/
public function setFirstResult($firstResult)
{
$this->firstResult = null === $firstResult ? null : (int) $firstResult;
$this->firstResult = $firstResult === null ? null : (int) $firstResult;
return $this;
}
@ -252,7 +215,7 @@ class Criteria
*/
public function setMaxResults($maxResults)
{
$this->maxResults = null === $maxResults ? null : (int) $maxResults;
$this->maxResults = $maxResults === null ? null : (int) $maxResults;
return $this;
}

View File

@ -1,32 +1,27 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections\Expr;
use ArrayAccess;
use Closure;
use RuntimeException;
use function in_array;
use function is_array;
use function is_scalar;
use function iterator_to_array;
use function method_exists;
use function preg_match;
use function preg_replace_callback;
use function strlen;
use function strpos;
use function strtoupper;
use function substr;
/**
* Walks an expression graph and turns it into a PHP closure.
*
* This closure can be used with {@Collection#filter()} and is used internally
* by {@ArrayCollection#select()}.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.3
*/
class ClosureExpressionVisitor extends ExpressionVisitor
{
@ -35,8 +30,8 @@ class ClosureExpressionVisitor extends ExpressionVisitor
* directly or indirectly (through an accessor get*, is*, or a magic
* method, __get, __call).
*
* @param object $object
* @param string $field
* @param object|array $object
* @param string $field
*
* @return mixed
*/
@ -46,16 +41,18 @@ class ClosureExpressionVisitor extends ExpressionVisitor
return $object[$field];
}
$accessors = array('get', 'is');
$accessors = ['get', 'is'];
foreach ($accessors as $accessor) {
$accessor .= $field;
if ( ! method_exists($object, $accessor)) {
continue;
if (method_exists($object, $accessor)) {
return $object->$accessor();
}
}
return $object->$accessor();
if (preg_match('/^is[A-Z]+/', $field) === 1 && method_exists($object, $field)) {
return $object->$field();
}
// __call should be triggered for get.
@ -65,7 +62,7 @@ class ClosureExpressionVisitor extends ExpressionVisitor
return $object->$accessor();
}
if ($object instanceof \ArrayAccess) {
if ($object instanceof ArrayAccess) {
return $object[$field];
}
@ -74,17 +71,16 @@ class ClosureExpressionVisitor extends ExpressionVisitor
}
// camelcase field name to support different variable naming conventions
$ccField = preg_replace_callback('/_(.?)/', function($matches) { return strtoupper($matches[1]); }, $field);
$ccField = preg_replace_callback('/_(.?)/', static function ($matches) {
return strtoupper($matches[1]);
}, $field);
foreach ($accessors as $accessor) {
$accessor .= $ccField;
if ( ! method_exists($object, $accessor)) {
continue;
if (method_exists($object, $accessor)) {
return $object->$accessor();
}
return $object->$accessor();
}
return $object->$field;
@ -93,29 +89,29 @@ class ClosureExpressionVisitor extends ExpressionVisitor
/**
* Helper for sorting arrays of objects based on multiple fields + orientations.
*
* @param string $name
* @param int $orientation
* @param \Closure $next
* @param string $name
* @param int $orientation
*
* @return \Closure
* @return Closure
*/
public static function sortByField($name, $orientation = 1, \Closure $next = null)
public static function sortByField($name, $orientation = 1, ?Closure $next = null)
{
if ( ! $next) {
$next = function() {
if (! $next) {
$next = static function () : int {
return 0;
};
}
return function ($a, $b) use ($name, $next, $orientation) {
return static function ($a, $b) use ($name, $next, $orientation) : int {
$aValue = ClosureExpressionVisitor::getObjectFieldValue($a, $name);
$bValue = ClosureExpressionVisitor::getObjectFieldValue($b, $name);
if ($aValue === $bValue) {
return $next($a, $b);
}
return (($aValue > $bValue) ? 1 : -1) * $orientation;
return ($aValue > $bValue ? 1 : -1) * $orientation;
};
}
@ -129,72 +125,77 @@ class ClosureExpressionVisitor extends ExpressionVisitor
switch ($comparison->getOperator()) {
case Comparison::EQ:
return function ($object) use ($field, $value) {
return static function ($object) use ($field, $value) : bool {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) === $value;
};
case Comparison::NEQ:
return function ($object) use ($field, $value) {
return static function ($object) use ($field, $value) : bool {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) !== $value;
};
case Comparison::LT:
return function ($object) use ($field, $value) {
return static function ($object) use ($field, $value) : bool {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) < $value;
};
case Comparison::LTE:
return function ($object) use ($field, $value) {
return static function ($object) use ($field, $value) : bool {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) <= $value;
};
case Comparison::GT:
return function ($object) use ($field, $value) {
return static function ($object) use ($field, $value) : bool {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) > $value;
};
case Comparison::GTE:
return function ($object) use ($field, $value) {
return static function ($object) use ($field, $value) : bool {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) >= $value;
};
case Comparison::IN:
return function ($object) use ($field, $value) {
return in_array(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value);
return static function ($object) use ($field, $value) : bool {
$fieldValue = ClosureExpressionVisitor::getObjectFieldValue($object, $field);
return in_array($fieldValue, $value, is_scalar($fieldValue));
};
case Comparison::NIN:
return function ($object) use ($field, $value) {
return ! in_array(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value);
return static function ($object) use ($field, $value) : bool {
$fieldValue = ClosureExpressionVisitor::getObjectFieldValue($object, $field);
return ! in_array($fieldValue, $value, is_scalar($fieldValue));
};
case Comparison::CONTAINS:
return function ($object) use ($field, $value) {
return false !== strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value);
return static function ($object) use ($field, $value) {
return strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value) !== false;
};
case Comparison::MEMBER_OF:
return function ($object) use ($field, $value) {
return static function ($object) use ($field, $value) : bool {
$fieldValues = ClosureExpressionVisitor::getObjectFieldValue($object, $field);
if (!is_array($fieldValues)) {
if (! is_array($fieldValues)) {
$fieldValues = iterator_to_array($fieldValues);
}
return in_array($value, $fieldValues);
return in_array($value, $fieldValues, true);
};
case Comparison::STARTS_WITH:
return function ($object) use ($field, $value) {
return 0 === strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value);
return static function ($object) use ($field, $value) : bool {
return strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value) === 0;
};
case Comparison::ENDS_WITH:
return function ($object) use ($field, $value) {
return static function ($object) use ($field, $value) : bool {
return $value === substr(ClosureExpressionVisitor::getObjectFieldValue($object, $field), -strlen($value));
};
default:
throw new \RuntimeException("Unknown comparison operator: " . $comparison->getOperator());
throw new RuntimeException('Unknown comparison operator: ' . $comparison->getOperator());
}
}
@ -211,54 +212,50 @@ class ClosureExpressionVisitor extends ExpressionVisitor
*/
public function walkCompositeExpression(CompositeExpression $expr)
{
$expressionList = array();
$expressionList = [];
foreach ($expr->getExpressionList() as $child) {
$expressionList[] = $this->dispatch($child);
}
switch($expr->getType()) {
switch ($expr->getType()) {
case CompositeExpression::TYPE_AND:
return $this->andExpressions($expressionList);
case CompositeExpression::TYPE_OR:
return $this->orExpressions($expressionList);
default:
throw new \RuntimeException("Unknown composite " . $expr->getType());
throw new RuntimeException('Unknown composite ' . $expr->getType());
}
}
/**
* @param array $expressions
*
* @return callable
*/
private function andExpressions($expressions)
private function andExpressions(array $expressions) : callable
{
return function ($object) use ($expressions) {
return static function ($object) use ($expressions) : bool {
foreach ($expressions as $expression) {
if ( ! $expression($object)) {
if (! $expression($object)) {
return false;
}
}
return true;
};
}
/**
* @param array $expressions
*
* @return callable
*/
private function orExpressions($expressions)
private function orExpressions(array $expressions) : callable
{
return function ($object) use ($expressions) {
return static function ($object) use ($expressions) : bool {
foreach ($expressions as $expression) {
if ($expression($object)) {
return true;
}
}
return false;
};
}

View File

@ -1,58 +1,33 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections\Expr;
/**
* Comparison of a field with a value by the given operator.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.3
*/
class Comparison implements Expression
{
const EQ = '=';
const NEQ = '<>';
const LT = '<';
const LTE = '<=';
const GT = '>';
const GTE = '>=';
const IS = '='; // no difference with EQ
const IN = 'IN';
const NIN = 'NIN';
const CONTAINS = 'CONTAINS';
const MEMBER_OF = 'MEMBER_OF';
const STARTS_WITH = 'STARTS_WITH';
const ENDS_WITH = 'ENDS_WITH';
/**
* @var string
*/
public const EQ = '=';
public const NEQ = '<>';
public const LT = '<';
public const LTE = '<=';
public const GT = '>';
public const GTE = '>=';
public const IS = '='; // no difference with EQ
public const IN = 'IN';
public const NIN = 'NIN';
public const CONTAINS = 'CONTAINS';
public const MEMBER_OF = 'MEMBER_OF';
public const STARTS_WITH = 'STARTS_WITH';
public const ENDS_WITH = 'ENDS_WITH';
/** @var string */
private $field;
/**
* @var string
*/
/** @var string */
private $op;
/**
* @var Value
*/
/** @var Value */
private $value;
/**
@ -62,12 +37,12 @@ class Comparison implements Expression
*/
public function __construct($field, $operator, $value)
{
if ( ! ($value instanceof Value)) {
if (! ($value instanceof Value)) {
$value = new Value($value);
}
$this->field = $field;
$this->op = $operator;
$this->op = $operator;
$this->value = $value;
}

View File

@ -1,50 +1,28 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections\Expr;
use RuntimeException;
/**
* Expression of Expressions combined by AND or OR operation.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.3
*/
class CompositeExpression implements Expression
{
const TYPE_AND = 'AND';
const TYPE_OR = 'OR';
public const TYPE_AND = 'AND';
public const TYPE_OR = 'OR';
/**
* @var string
*/
/** @var string */
private $type;
/**
* @var Expression[]
*/
private $expressions = array();
/** @var Expression[] */
private $expressions = [];
/**
* @param string $type
* @param array $expressions
*
* @throws \RuntimeException
* @throws RuntimeException
*/
public function __construct($type, array $expressions)
{
@ -52,10 +30,10 @@ class CompositeExpression implements Expression
foreach ($expressions as $expr) {
if ($expr instanceof Value) {
throw new \RuntimeException("Values are not supported expressions as children of and/or expressions.");
throw new RuntimeException('Values are not supported expressions as children of and/or expressions.');
}
if ( ! ($expr instanceof Expression)) {
throw new \RuntimeException("No expression given to CompositeExpression.");
if (! ($expr instanceof Expression)) {
throw new RuntimeException('No expression given to CompositeExpression.');
}
$this->expressions[] = $expr;

View File

@ -1,34 +1,13 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections\Expr;
/**
* Expression for the {@link Selectable} interface.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
interface Expression
{
/**
* @param ExpressionVisitor $visitor
*
* @return mixed
*/
public function visit(ExpressionVisitor $visitor);

View File

@ -1,37 +1,19 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections\Expr;
use RuntimeException;
use function get_class;
/**
* An Expression visitor walks a graph of expressions and turns them into a
* query for the underlying implementation.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
abstract class ExpressionVisitor
{
/**
* Converts a comparison expression into the target query language output.
*
* @param Comparison $comparison
*
* @return mixed
*/
abstract public function walkComparison(Comparison $comparison);
@ -39,8 +21,6 @@ abstract class ExpressionVisitor
/**
* Converts a value expression into the target query language part.
*
* @param Value $value
*
* @return mixed
*/
abstract public function walkValue(Value $value);
@ -48,8 +28,6 @@ abstract class ExpressionVisitor
/**
* Converts a composite expression into the target query language output.
*
* @param CompositeExpression $expr
*
* @return mixed
*/
abstract public function walkCompositeExpression(CompositeExpression $expr);
@ -57,26 +35,21 @@ abstract class ExpressionVisitor
/**
* Dispatches walking an expression to the appropriate handler.
*
* @param Expression $expr
*
* @return mixed
*
* @throws \RuntimeException
* @throws RuntimeException
*/
public function dispatch(Expression $expr)
{
switch (true) {
case ($expr instanceof Comparison):
case $expr instanceof Comparison:
return $this->walkComparison($expr);
case ($expr instanceof Value):
case $expr instanceof Value:
return $this->walkValue($expr);
case ($expr instanceof CompositeExpression):
case $expr instanceof CompositeExpression:
return $this->walkCompositeExpression($expr);
default:
throw new \RuntimeException("Unknown Expression " . get_class($expr));
throw new RuntimeException('Unknown Expression ' . get_class($expr));
}
}
}

View File

@ -1,29 +1,10 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections\Expr;
class Value implements Expression
{
/**
* @var mixed
*/
/** @var mixed */
private $value;
/**

View File

@ -1,27 +1,11 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\Common\Collections\Expr\CompositeExpression;
use Doctrine\Common\Collections\Expr\Value;
use function func_get_args;
/**
* Builder for Expressions in the {@link Selectable} interface.
@ -29,14 +13,11 @@ use Doctrine\Common\Collections\Expr\Value;
* Important Notice for interoperable code: You have to use scalar
* values only for comparisons, otherwise the behavior of the comparison
* may be different between implementations (Array vs ORM vs ODM).
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.3
*/
class ExpressionBuilder
{
/**
* @param mixed $x
* @param mixed ...$x
*
* @return CompositeExpression
*/
@ -46,7 +27,7 @@ class ExpressionBuilder
}
/**
* @param mixed $x
* @param mixed ...$x
*
* @return CompositeExpression
*/
@ -133,7 +114,7 @@ class ExpressionBuilder
/**
* @param string $field
* @param mixed $values
* @param array $values
*
* @return Comparison
*/
@ -144,7 +125,7 @@ class ExpressionBuilder
/**
* @param string $field
* @param mixed $values
* @param array $values
*
* @return Comparison
*/
@ -170,7 +151,7 @@ class ExpressionBuilder
*
* @return Comparison
*/
public function memberOf ($field, $value)
public function memberOf($field, $value)
{
return new Comparison($field, Comparison::MEMBER_OF, new Value($value));
}
@ -195,6 +176,5 @@ class ExpressionBuilder
public function endsWith($field, $value)
{
return new Comparison($field, Comparison::ENDS_WITH, new Value($value));
}
}
}

View File

@ -1,21 +1,4 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections;
@ -31,8 +14,9 @@ namespace Doctrine\Common\Collections;
* this API can implement efficient database access without having to ask the
* EntityManager or Repositories.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.3
* @phpstan-template TKey
* @psalm-template TKey as array-key
* @psalm-template T
*/
interface Selectable
{
@ -40,9 +24,9 @@ interface Selectable
* Selects all elements from a selectable that match the expression and
* returns a new collection containing these elements.
*
* @param Criteria $criteria
*
* @return Collection
*
* @psalm-return Collection<TKey,T>
*/
public function matching(Criteria $criteria);
}

View File

@ -1,6 +1,8 @@
# Doctrine Inflector
Doctrine Inflector is a small library that can perform string manipulations
with regard to upper-/lowercase and singular/plural forms of words.
with regard to uppercase/lowercase and singular/plural forms of words.
[![Build Status](https://travis-ci.org/doctrine/inflector.svg?branch=master)](https://travis-ci.org/doctrine/inflector)
[![Build Status](https://travis-ci.org/doctrine/inflector.svg)](https://travis-ci.org/doctrine/inflector)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/doctrine/inflector/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/doctrine/inflector/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/doctrine/inflector/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/doctrine/inflector/?branch=master)

View File

@ -1,9 +1,9 @@
{
"name": "doctrine/inflector",
"type": "library",
"description": "Common String Manipulations with regard to casing and singular/plural rules.",
"keywords": ["string", "inflection", "singularize", "pluralize"],
"homepage": "http://www.doctrine-project.org",
"description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.",
"keywords": ["php", "strings", "words", "manipulation", "inflector", "inflection", "uppercase", "lowercase", "singular", "plural"],
"homepage": "https://www.doctrine-project.org/projects/inflector.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
@ -13,20 +13,30 @@
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
],
"require": {
"php": "^7.0"
"php": "^7.2 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^6.2"
"doctrine/coding-standard": "^7.0",
"phpstan/phpstan": "^0.11",
"phpstan/phpstan-phpunit": "^0.11",
"phpstan/phpstan-strict-rules": "^0.11",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
},
"autoload": {
"psr-4": { "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" }
"psr-4": {
"Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector",
"Doctrine\\Inflector\\": "lib/Doctrine/Inflector"
}
},
"autoload-dev": {
"psr-4": { "Doctrine\\Tests\\Common\\Inflector\\": "tests/Doctrine/Common/Inflector" }
"psr-4": {
"Doctrine\\Tests\\Common\\Inflector\\": "tests/Doctrine/Tests/Common/Inflector",
"Doctrine\\Tests\\Inflector\\": "tests/Doctrine/Tests/Inflector"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.2.x-dev"
"dev-master": "2.0.x-dev"
}
}
}

View File

@ -19,277 +19,97 @@
namespace Doctrine\Common\Inflector;
use Doctrine\Inflector\Inflector as InflectorObject;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\Inflector\LanguageInflectorFactory;
use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Transformations;
use Doctrine\Inflector\Rules\Word;
use InvalidArgumentException;
use function array_keys;
use function array_map;
use function array_unshift;
use function array_values;
use function sprintf;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Doctrine inflector has static methods for inflecting text.
*
* The methods in these classes are from several different sources collected
* across several different php projects and several different authors. The
* original author names and emails are not known.
*
* Pluralize & Singularize implementation are borrowed from CakePHP with some modifications.
*
* @link www.doctrine-project.org
* @since 1.0
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Jonathan H. Wage <jonwage@gmail.com>
* @deprecated
*/
class Inflector
{
/**
* Plural inflector rules.
*
* @var array
* @var LanguageInflectorFactory|null
*/
private static $plural = array(
'rules' => array(
'/(s)tatus$/i' => '\1\2tatuses',
'/(quiz)$/i' => '\1zes',
'/^(ox)$/i' => '\1\2en',
'/([m|l])ouse$/i' => '\1ice',
'/(matr|vert|ind)(ix|ex)$/i' => '\1ices',
'/(x|ch|ss|sh)$/i' => '\1es',
'/([^aeiouy]|qu)y$/i' => '\1ies',
'/(hive|gulf)$/i' => '\1s',
'/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
'/sis$/i' => 'ses',
'/([ti])um$/i' => '\1a',
'/(p)erson$/i' => '\1eople',
'/(m)an$/i' => '\1en',
'/(c)hild$/i' => '\1hildren',
'/(f)oot$/i' => '\1eet',
'/(buffal|her|potat|tomat|volcan)o$/i' => '\1\2oes',
'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i',
'/us$/i' => 'uses',
'/(alias)$/i' => '\1es',
'/(analys|ax|cris|test|thes)is$/i' => '\1es',
'/s$/' => 's',
'/^$/' => '',
'/$/' => 's',
),
'uninflected' => array(
'.*[nrlm]ese',
'.*deer',
'.*fish',
'.*measles',
'.*ois',
'.*pox',
'.*sheep',
'people',
'cookie',
'police',
),
'irregular' => array(
'atlas' => 'atlases',
'axe' => 'axes',
'beef' => 'beefs',
'brother' => 'brothers',
'cafe' => 'cafes',
'chateau' => 'chateaux',
'niveau' => 'niveaux',
'child' => 'children',
'cookie' => 'cookies',
'corpus' => 'corpuses',
'cow' => 'cows',
'criterion' => 'criteria',
'curriculum' => 'curricula',
'demo' => 'demos',
'domino' => 'dominoes',
'echo' => 'echoes',
'foot' => 'feet',
'fungus' => 'fungi',
'ganglion' => 'ganglions',
'genie' => 'genies',
'genus' => 'genera',
'graffito' => 'graffiti',
'hippopotamus' => 'hippopotami',
'hoof' => 'hoofs',
'human' => 'humans',
'iris' => 'irises',
'larva' => 'larvae',
'leaf' => 'leaves',
'loaf' => 'loaves',
'man' => 'men',
'medium' => 'media',
'memorandum' => 'memoranda',
'money' => 'monies',
'mongoose' => 'mongooses',
'motto' => 'mottoes',
'move' => 'moves',
'mythos' => 'mythoi',
'niche' => 'niches',
'nucleus' => 'nuclei',
'numen' => 'numina',
'occiput' => 'occiputs',
'octopus' => 'octopuses',
'opus' => 'opuses',
'ox' => 'oxen',
'passerby' => 'passersby',
'penis' => 'penises',
'person' => 'people',
'plateau' => 'plateaux',
'runner-up' => 'runners-up',
'sex' => 'sexes',
'soliloquy' => 'soliloquies',
'son-in-law' => 'sons-in-law',
'syllabus' => 'syllabi',
'testis' => 'testes',
'thief' => 'thieves',
'tooth' => 'teeth',
'tornado' => 'tornadoes',
'trilby' => 'trilbys',
'turf' => 'turfs',
'volcano' => 'volcanoes',
)
);
private static $factory;
/**
* Singular inflector rules.
*
* @var array
*/
private static $singular = array(
'rules' => array(
'/(s)tatuses$/i' => '\1\2tatus',
'/^(.*)(menu)s$/i' => '\1\2',
'/(quiz)zes$/i' => '\\1',
'/(matr)ices$/i' => '\1ix',
'/(vert|ind)ices$/i' => '\1ex',
'/^(ox)en/i' => '\1',
'/(alias)(es)*$/i' => '\1',
'/(buffal|her|potat|tomat|volcan)oes$/i' => '\1o',
'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us',
'/([ftw]ax)es/i' => '\1',
'/(analys|ax|cris|test|thes)es$/i' => '\1is',
'/(shoe|slave)s$/i' => '\1',
'/(o)es$/i' => '\1',
'/ouses$/' => 'ouse',
'/([^a])uses$/' => '\1us',
'/([m|l])ice$/i' => '\1ouse',
'/(x|ch|ss|sh)es$/i' => '\1',
'/(m)ovies$/i' => '\1\2ovie',
'/(s)eries$/i' => '\1\2eries',
'/([^aeiouy]|qu)ies$/i' => '\1y',
'/([lr])ves$/i' => '\1f',
'/(tive)s$/i' => '\1',
'/(hive)s$/i' => '\1',
'/(drive)s$/i' => '\1',
'/(dive)s$/i' => '\1',
'/([^fo])ves$/i' => '\1fe',
'/(^analy)ses$/i' => '\1sis',
'/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
'/([ti])a$/i' => '\1um',
'/(p)eople$/i' => '\1\2erson',
'/(m)en$/i' => '\1an',
'/(c)hildren$/i' => '\1\2hild',
'/(f)eet$/i' => '\1oot',
'/(n)ews$/i' => '\1\2ews',
'/eaus$/' => 'eau',
'/^(.*us)$/' => '\\1',
'/s$/i' => '',
),
'uninflected' => array(
'.*[nrlm]ese',
'.*deer',
'.*fish',
'.*measles',
'.*ois',
'.*pox',
'.*sheep',
'.*ss',
'police',
'pants',
'clothes',
),
'irregular' => array(
'caches' => 'cache',
'criteria' => 'criterion',
'curves' => 'curve',
'emphases' => 'emphasis',
'foes' => 'foe',
'hoaxes' => 'hoax',
'media' => 'medium',
'neuroses' => 'neurosis',
'waves' => 'wave',
'oases' => 'oasis',
)
);
/** @var InflectorObject|null */
private static $instance;
/**
* Words that should not be inflected.
*
* @var array
*/
private static $uninflected = array(
'Amoyese', 'bison', 'Borghese', 'bream', 'breeches', 'britches', 'buffalo', 'cantus',
'carp', 'chassis', 'clippers', 'cod', 'coitus', 'Congoese', 'contretemps', 'corps',
'debris', 'diabetes', 'djinn', 'eland', 'elk', 'equipment', 'Faroese', 'flounder',
'Foochowese', 'Furniture', 'gallows', 'Genevese', 'Genoese', 'Gilbertese', 'graffiti',
'headquarters', 'herpes', 'hijinks', 'Hottentotese', 'information', 'innings',
'jackanapes', 'Kiplingese', 'Kongoese', 'Lucchese', 'Luggage', 'mackerel', 'Maltese', '.*?media',
'mews', 'moose', 'mumps', 'Nankingese', 'news', 'nexus', 'Niasese',
'Pekingese', 'Piedmontese', 'pincers', 'Pistoiese', 'pliers', 'Portuguese',
'proceedings', 'rabies', 'rice', 'rhinoceros', 'salmon', 'Sarawakese', 'scissors',
'sea[- ]bass', 'series', 'Shavese', 'shears', 'siemens', 'species', 'staff', 'swine',
'testes', 'trousers', 'trout', 'tuna', 'Vermontese', 'Wenchowese', 'whiting',
'wildebeest', 'Yengeese'
);
private static function getInstance() : InflectorObject
{
if (self::$factory === null) {
self::$factory = self::createFactory();
}
/**
* Method cache array.
*
* @var array
*/
private static $cache = array();
if (self::$instance === null) {
self::$instance = self::$factory->build();
}
/**
* The initial state of Inflector so reset() works.
*
* @var array
*/
private static $initialState = array();
return self::$instance;
}
private static function createFactory() : LanguageInflectorFactory
{
return InflectorFactory::create();
}
/**
* Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'.
*
* @param string $word The word to tableize.
*
* @return string The tableized word.
* @deprecated
*/
public static function tableize($word)
public static function tableize(string $word) : string
{
return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word));
@trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please update to the new Inflector API.', __METHOD__), E_USER_DEPRECATED);
return self::getInstance()->tableize($word);
}
/**
* Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'.
*
* @param string $word The word to classify.
*
* @return string The classified word.
*/
public static function classify($word)
public static function classify(string $word) : string
{
return str_replace(' ', '', ucwords(strtr($word, '_-', ' ')));
@trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please update to the new Inflector API.', __METHOD__), E_USER_DEPRECATED);
return self::getInstance()->classify($word);
}
/**
* Camelizes a word. This uses the classify() method and turns the first character to lowercase.
*
* @param string $word The word to camelize.
*
* @return string The camelized word.
* @deprecated
*/
public static function camelize($word)
public static function camelize(string $word) : string
{
return lcfirst(self::classify($word));
@trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please update to the new Inflector API.', __METHOD__), E_USER_DEPRECATED);
return self::getInstance()->camelize($word);
}
/**
* Uppercases words with configurable delimeters between words.
* Uppercases words with configurable delimiters between words.
*
* Takes a string and capitalizes all of the words, like PHP's built-in
* ucwords function. This extends that behavior, however, by allowing the
* word delimeters to be configured, rather than only separating on
* ucwords function. This extends that behavior, however, by allowing the
* word delimiters to be configured, rather than only separating on
* whitespace.
*
* Here is an example:
@ -307,38 +127,29 @@ class Inflector
* @param string $string The string to operate on.
* @param string $delimiters A list of word separators.
*
* @return string The string with all delimeter-separated words capitalized.
* @return string The string with all delimiter-separated words capitalized.
*
* @deprecated
*/
public static function ucwords($string, $delimiters = " \n\t\r\0\x0B-")
public static function ucwords(string $string, string $delimiters = " \n\t\r\0\x0B-") : string
{
return preg_replace_callback(
'/[^' . preg_quote($delimiters, '/') . ']+/',
function($matches) {
return ucfirst($matches[0]);
},
$string
);
@trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please use the "ucwords" function instead.', __METHOD__), E_USER_DEPRECATED);
return ucwords($string, $delimiters);
}
/**
* Clears Inflectors inflected value caches, and resets the inflection
* rules to the initial values.
*
* @return void
* @deprecated
*/
public static function reset()
public static function reset() : void
{
if (empty(self::$initialState)) {
self::$initialState = get_class_vars('Inflector');
@trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please update to the new Inflector API.', __METHOD__), E_USER_DEPRECATED);
return;
}
foreach (self::$initialState as $key => $val) {
if ($key != 'initialState') {
self::${$key} = $val;
}
}
self::$factory = null;
self::$instance = null;
}
/**
@ -355,42 +166,85 @@ class Inflector
* ));
* }}}
*
* @param string $type The type of inflection, either 'plural' or 'singular'
* @param array $rules An array of rules to be added.
* @param boolean $reset If true, will unset default inflections for all
* new rules that are being defined in $rules.
* @param string $type The type of inflection, either 'plural' or 'singular'
* @param array|iterable $rules An array of rules to be added.
* @param boolean $reset If true, will unset default inflections for all
* new rules that are being defined in $rules.
*
* @return void
*
* @deprecated
*/
public static function rules($type, $rules, $reset = false)
public static function rules(string $type, iterable $rules, bool $reset = false) : void
{
@trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please update to the new Inflector API.', __METHOD__), E_USER_DEPRECATED);
if (self::$factory === null) {
self::$factory = self::createFactory();
}
self::$instance = null;
switch ($type) {
case 'singular':
self::$factory->withSingularRules(self::buildRuleset($rules), $reset);
break;
case 'plural':
self::$factory->withPluralRules(self::buildRuleset($rules), $reset);
break;
default:
throw new InvalidArgumentException(sprintf('Cannot define custom inflection rules for type "%s".', $type));
}
}
private static function buildRuleset(iterable $rules) : Ruleset
{
$regular = [];
$irregular = [];
$uninflected = [];
foreach ($rules as $rule => $pattern) {
if ( ! is_array($pattern)) {
$regular[$rule] = $pattern;
continue;
}
if ($reset) {
self::${$type}[$rule] = $pattern;
} else {
self::${$type}[$rule] = ($rule === 'uninflected')
? array_merge($pattern, self::${$type}[$rule])
: $pattern + self::${$type}[$rule];
}
unset($rules[$rule], self::${$type}['cache' . ucfirst($rule)]);
if (isset(self::${$type}['merged'][$rule])) {
unset(self::${$type}['merged'][$rule]);
}
if ($type === 'plural') {
self::$cache['pluralize'] = self::$cache['tableize'] = array();
} elseif ($type === 'singular') {
self::$cache['singularize'] = array();
switch ($rule) {
case 'uninflected':
$uninflected = $pattern;
break;
case 'irregular':
$irregular = $pattern;
break;
case 'rules':
$regular = $pattern;
break;
}
}
self::${$type}['rules'] = $rules + self::${$type}['rules'];
return new Ruleset(
new Transformations(...array_map(
static function (string $pattern, string $replacement) : Transformation {
return new Transformation(new Pattern($pattern), $replacement);
},
array_keys($regular),
array_values($regular)
)),
new Patterns(...array_map(
static function (string $pattern) : Pattern {
return new Pattern($pattern);
},
$uninflected
)),
new Substitutions(...array_map(
static function (string $word, string $to) : Substitution {
return new Substitution(new Word($word), new Word($to));
},
array_keys($irregular),
array_values($irregular)
))
);
}
/**
@ -399,45 +253,14 @@ class Inflector
* @param string $word The word in singular form.
*
* @return string The word in plural form.
*
* @deprecated
*/
public static function pluralize($word)
public static function pluralize(string $word) : string
{
if (isset(self::$cache['pluralize'][$word])) {
return self::$cache['pluralize'][$word];
}
@trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please update to the new Inflector API.', __METHOD__), E_USER_DEPRECATED);
if (!isset(self::$plural['merged']['irregular'])) {
self::$plural['merged']['irregular'] = self::$plural['irregular'];
}
if (!isset(self::$plural['merged']['uninflected'])) {
self::$plural['merged']['uninflected'] = array_merge(self::$plural['uninflected'], self::$uninflected);
}
if (!isset(self::$plural['cacheUninflected']) || !isset(self::$plural['cacheIrregular'])) {
self::$plural['cacheUninflected'] = '(?:' . implode('|', self::$plural['merged']['uninflected']) . ')';
self::$plural['cacheIrregular'] = '(?:' . implode('|', array_keys(self::$plural['merged']['irregular'])) . ')';
}
if (preg_match('/(.*)\\b(' . self::$plural['cacheIrregular'] . ')$/i', $word, $regs)) {
self::$cache['pluralize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$plural['merged']['irregular'][strtolower($regs[2])], 1);
return self::$cache['pluralize'][$word];
}
if (preg_match('/^(' . self::$plural['cacheUninflected'] . ')$/i', $word, $regs)) {
self::$cache['pluralize'][$word] = $word;
return $word;
}
foreach (self::$plural['rules'] as $rule => $replacement) {
if (preg_match($rule, $word)) {
self::$cache['pluralize'][$word] = preg_replace($rule, $replacement, $word);
return self::$cache['pluralize'][$word];
}
}
return self::getInstance()->pluralize($word);
}
/**
@ -446,54 +269,13 @@ class Inflector
* @param string $word The word in plural form.
*
* @return string The word in singular form.
*
* @deprecated
*/
public static function singularize($word)
public static function singularize(string $word) : string
{
if (isset(self::$cache['singularize'][$word])) {
return self::$cache['singularize'][$word];
}
@trigger_error(sprintf('The "%s" method is deprecated and will be dropped in doctrine/inflector 2.0. Please update to the new Inflector API.', __METHOD__), E_USER_DEPRECATED);
if (!isset(self::$singular['merged']['uninflected'])) {
self::$singular['merged']['uninflected'] = array_merge(
self::$singular['uninflected'],
self::$uninflected
);
}
if (!isset(self::$singular['merged']['irregular'])) {
self::$singular['merged']['irregular'] = array_merge(
self::$singular['irregular'],
array_flip(self::$plural['irregular'])
);
}
if (!isset(self::$singular['cacheUninflected']) || !isset(self::$singular['cacheIrregular'])) {
self::$singular['cacheUninflected'] = '(?:' . join('|', self::$singular['merged']['uninflected']) . ')';
self::$singular['cacheIrregular'] = '(?:' . join('|', array_keys(self::$singular['merged']['irregular'])) . ')';
}
if (preg_match('/(.*)\\b(' . self::$singular['cacheIrregular'] . ')$/i', $word, $regs)) {
self::$cache['singularize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$singular['merged']['irregular'][strtolower($regs[2])], 1);
return self::$cache['singularize'][$word];
}
if (preg_match('/^(' . self::$singular['cacheUninflected'] . ')$/i', $word, $regs)) {
self::$cache['singularize'][$word] = $word;
return $word;
}
foreach (self::$singular['rules'] as $rule => $replacement) {
if (preg_match($rule, $word)) {
self::$cache['singularize'][$word] = preg_replace($rule, $replacement, $word);
return self::$cache['singularize'][$word];
}
}
self::$cache['singularize'][$word] = $word;
return $word;
return self::getInstance()->singularize($word);
}
}

View File

@ -1,5 +1,7 @@
# Doctrine Lexer
Build Status: [![Build Status](https://travis-ci.org/doctrine/lexer.svg?branch=master)](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).

View File

@ -17,10 +17,12 @@
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
],
"require": {
"php": ">=5.3.2"
"php": "^7.2 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^4.5"
"doctrine/coding-standard": "^6.0",
"phpstan/phpstan": "^0.11.8",
"phpunit/phpunit": "^8.2"
},
"autoload": {
"psr-4": { "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" }
@ -30,7 +32,10 @@
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
"dev-master": "1.2.x-dev"
}
},
"config": {
"sort-packages": true
}
}

View File

@ -1,31 +1,21 @@
<?php
/*
* 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.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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.
*
* @since 2.0
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
abstract class AbstractLexer
{
@ -47,36 +37,43 @@ abstract class AbstractLexer
*
* @var array
*/
private $tokens = array();
private $tokens = [];
/**
* Current lexer position in input string.
*
* @var integer
* @var int
*/
private $position = 0;
/**
* Current peek of current lexer position.
*
* @var integer
* @var int
*/
private $peek = 0;
/**
* The next token in the input.
*
* @var array
* @var array|null
*/
public $lookahead;
/**
* The last matched/seen token.
*
* @var array
* @var array|null
*/
public $token;
/**
* Composed regex for input parsing.
*
* @var string
*/
private $regex;
/**
* Sets the input data to be tokenized.
*
@ -90,7 +87,7 @@ abstract class AbstractLexer
public function setInput($input)
{
$this->input = $input;
$this->tokens = array();
$this->tokens = [];
$this->reset();
$this->scan($input);
@ -104,9 +101,9 @@ abstract class AbstractLexer
public function reset()
{
$this->lookahead = null;
$this->token = null;
$this->peek = 0;
$this->position = 0;
$this->token = null;
$this->peek = 0;
$this->position = 0;
}
/**
@ -122,7 +119,7 @@ abstract class AbstractLexer
/**
* Resets the lexer position on the input to the given position.
*
* @param integer $position Position to place the lexical scanner.
* @param int $position Position to place the lexical scanner.
*
* @return void
*/
@ -134,7 +131,7 @@ abstract class AbstractLexer
/**
* Retrieve the original lexer's input until a given position.
*
* @param integer $position
* @param int $position
*
* @return string
*/
@ -146,13 +143,13 @@ abstract class AbstractLexer
/**
* Checks whether a given token matches the current lookahead.
*
* @param integer|string $token
* @param int|string $token
*
* @return boolean
* @return bool
*/
public function isNextToken($token)
{
return null !== $this->lookahead && $this->lookahead['type'] === $token;
return $this->lookahead !== null && $this->lookahead['type'] === $token;
}
/**
@ -160,23 +157,23 @@ abstract class AbstractLexer
*
* @param array $tokens
*
* @return boolean
* @return bool
*/
public function isNextTokenAny(array $tokens)
{
return null !== $this->lookahead && in_array($this->lookahead['type'], $tokens, true);
return $this->lookahead !== null && in_array($this->lookahead['type'], $tokens, true);
}
/**
* Moves to the next token in the input string.
*
* @return boolean
* @return bool
*/
public function moveNext()
{
$this->peek = 0;
$this->token = $this->lookahead;
$this->lookahead = (isset($this->tokens[$this->position]))
$this->peek = 0;
$this->token = $this->lookahead;
$this->lookahead = isset($this->tokens[$this->position])
? $this->tokens[$this->position++] : null;
return $this->lookahead !== null;
@ -199,10 +196,10 @@ abstract class AbstractLexer
/**
* Checks if given value is identical to the given token.
*
* @param mixed $value
* @param integer $token
* @param mixed $value
* @param int|string $token
*
* @return boolean
* @return bool
*/
public function isA($value, $token)
{
@ -218,9 +215,9 @@ abstract class AbstractLexer
{
if (isset($this->tokens[$this->position + $this->peek])) {
return $this->tokens[$this->position + $this->peek++];
} else {
return null;
}
return null;
}
/**
@ -230,8 +227,9 @@ abstract class AbstractLexer
*/
public function glimpse()
{
$peek = $this->peek();
$peek = $this->peek();
$this->peek = 0;
return $peek;
}
@ -244,10 +242,8 @@ abstract class AbstractLexer
*/
protected function scan($input)
{
static $regex;
if ( ! isset($regex)) {
$regex = sprintf(
if (! isset($this->regex)) {
$this->regex = sprintf(
'/(%s)|%s/%s',
implode(')|(', $this->getCatchablePatterns()),
implode('|', $this->getNonCatchablePatterns()),
@ -255,37 +251,37 @@ abstract class AbstractLexer
);
}
$flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE;
$matches = preg_split($regex, $input, -1, $flags);
$flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE;
$matches = preg_split($this->regex, $input, -1, $flags);
if (false === $matches) {
if ($matches === false) {
// Work around https://bugs.php.net/78122
$matches = array(array($input, 0));
$matches = [[$input, 0]];
}
foreach ($matches as $match) {
// Must remain before 'value' assignment since it can change content
$type = $this->getType($match[0]);
$this->tokens[] = array(
$this->tokens[] = [
'value' => $match[0],
'type' => $type,
'position' => $match[1],
);
];
}
}
/**
* Gets the literal for a given token.
*
* @param integer $token
* @param int|string $token
*
* @return string
* @return int|string
*/
public function getLiteral($token)
{
$className = get_class($this);
$reflClass = new \ReflectionClass($className);
$className = static::class;
$reflClass = new ReflectionClass($className);
$constants = $reflClass->getConstants();
foreach ($constants as $name => $value) {
@ -304,7 +300,7 @@ abstract class AbstractLexer
*/
protected function getModifiers()
{
return 'i';
return 'iu';
}
/**
@ -326,7 +322,7 @@ abstract class AbstractLexer
*
* @param string $value
*
* @return integer
* @return int|string|null
*/
abstract protected function getType(&$value);
}

View File

@ -1,4 +1,4 @@
Copyright (c) 2013-2016 Eduardo Gulias Davis
Copyright (c) 2013-2021 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

View File

@ -9,18 +9,18 @@
],
"extra": {
"branch-alias": {
"dev-master": "2.1.x-dev"
"dev-master": "3.0.x-dev"
}
},
"require": {
"php": ">=5.5",
"doctrine/lexer": "^1.0.1",
"symfony/polyfill-intl-idn": "^1.10"
"php": ">=7.2",
"doctrine/lexer": "^1.2",
"symfony/polyfill-intl-idn": "^1.15"
},
"require-dev": {
"dominicsayers/isemail": "^3.0.7",
"phpunit/phpunit": "^4.8.36|^7.5.15",
"satooshi/php-coveralls": "^1.0.1"
"php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^8.5.8|^9.3.3",
"vimeo/psalm": "^4"
},
"suggest": {
"ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"

View File

@ -7,37 +7,52 @@ 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 C_NUL = 0;
const S_HTAB = 9;
const S_LF = 10;
const S_CR = 13;
const S_SP = 32;
const EXCLAMATION = 33;
const S_DQUOTE = 34;
const NUMBER_SIGN = 35;
const DOLLAR = 36;
const PERCENTAGE = 37;
const AMPERSAND = 38;
const S_SQUOTE = 39;
const S_OPENPARENTHESIS = 40;
const S_CLOSEPARENTHESIS = 41;
const ASTERISK = 42;
const S_PLUS = 43;
const S_COMMA = 44;
const S_HYPHEN = 45;
const S_DOT = 46;
const S_SLASH = 47;
const S_COLON = 58;
const S_SEMICOLON = 59;
const S_LOWERTHAN = 60;
const S_EQUAL = 61;
const S_GREATERTHAN = 62;
const QUESTIONMARK = 63;
const S_AT = 64;
const S_OPENBRACKET = 91;
const S_BACKSLASH = 92;
const S_CLOSEBRACKET = 93;
const CARET = 94;
const S_UNDERSCORE = 95;
const S_BACKTICK = 96;
const S_OPENCURLYBRACES = 123;
const S_PIPE = 124;
const S_CLOSECURLYBRACES = 125;
const S_TILDE = 126;
const C_DEL = 127;
const INVERT_QUESTIONMARK= 168;
const INVERT_EXCLAMATION = 173;
const GENERIC = 300;
const CRLF = 301;
const S_IPV6TAG = 301;
const INVALID = 302;
const CRLF = 1310;
const S_DOUBLECOLON = 5858;
const ASCII_INVALID_FROM = 127;
const ASCII_INVALID_TO = 199;
@ -47,6 +62,8 @@ class EmailLexer extends AbstractLexer
* @var array
*/
protected $charValue = array(
'{' => self::S_OPENCURLYBRACES,
'}' => self::S_CLOSECURLYBRACES,
'(' => self::S_OPENPARENTHESIS,
')' => self::S_CLOSEPARENTHESIS,
'<' => self::S_LOWERTHAN,
@ -71,10 +88,23 @@ class EmailLexer extends AbstractLexer
"\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,
'*' => self::ASTERISK,
'!' => self::EXCLAMATION,
'&' => self::AMPERSAND,
'^' => self::CARET,
'$' => self::DOLLAR,
'%' => self::PERCENTAGE,
'~' => self::S_TILDE,
'|' => self::S_PIPE,
'_' => self::S_UNDERSCORE,
'=' => self::S_EQUAL,
'+' => self::S_PLUS,
'¿' => self::INVERT_QUESTIONMARK,
'?' => self::QUESTIONMARK,
'#' => self::NUMBER_SIGN,
'¡' => self::INVERT_EXCLAMATION,
);
/**
@ -94,7 +124,9 @@ class EmailLexer extends AbstractLexer
*
* @var array
*
* @psalm-suppress NonInvariantDocblockPropertyType
* @psalm-var array{value:string, type:null|int, position:int}
* @psalm-suppress NonInvariantDocblockPropertyType
*/
public $token;
@ -114,6 +146,16 @@ class EmailLexer extends AbstractLexer
'position' => 0,
];
/**
* @var string
*/
private $accumulator = '';
/**
* @var bool
*/
private $hasToRecord = false;
public function __construct()
{
$this->previous = $this->token = self::$nullToken;
@ -173,10 +215,18 @@ class EmailLexer extends AbstractLexer
*/
public function moveNext()
{
if ($this->hasToRecord && $this->previous === self::$nullToken) {
$this->accumulator .= $this->token['value'];
}
$this->previous = $this->token;
$hasNext = parent::moveNext();
$this->token = $this->token ?: self::$nullToken;
if ($this->hasToRecord) {
$this->accumulator .= $this->token['value'];
}
return $hasNext;
}
@ -188,7 +238,7 @@ class EmailLexer extends AbstractLexer
protected function getCatchablePatterns()
{
return array(
'[a-zA-Z_]+[46]?', //ASCII and domain literal
'[a-zA-Z]+[46]?', //ASCII and domain literal
'[^\x00-\x7F]', //UTF-8
'[0-9]+',
'\r\n',
@ -205,7 +255,9 @@ class EmailLexer extends AbstractLexer
*/
protected function getNonCatchablePatterns()
{
return array('[\xA0-\xff]+');
return [
'[\xA0-\xff]+',
];
}
/**
@ -217,28 +269,38 @@ class EmailLexer extends AbstractLexer
*/
protected function getType(&$value)
{
if ($this->isNullType($value)) {
$encoded = $value;
if (mb_detect_encoding($value, 'auto', true) !== 'UTF-8') {
$encoded = utf8_encode($value);
}
if ($this->isValid($encoded)) {
return $this->charValue[$encoded];
}
if ($this->isNullType($encoded)) {
return self::C_NUL;
}
if ($this->isValid($value)) {
return $this->charValue[$value];
}
if ($this->isUTF8Invalid($value)) {
if ($this->isInvalidChar($encoded)) {
$this->hasInvalidTokens = true;
return self::INVALID;
}
return self::GENERIC;
}
/**
* @param string $value
*
* @return bool
*/
protected function isValid($value)
protected function isInvalidChar(string $value) : bool
{
if(preg_match("/[^\p{S}\p{C}\p{Cc}]+/iu", $value) ) {
return false;
}
return true;
}
protected function isValid(string $value) : bool
{
if (isset($this->charValue[$value])) {
return true;
@ -260,11 +322,7 @@ class EmailLexer extends AbstractLexer
return false;
}
/**
* @param string $value
* @return bool
*/
protected function isUTF8Invalid($value)
protected function isUTF8Invalid(string $value) : bool
{
if (preg_match('/\p{Cc}+/u', $value)) {
return true;
@ -280,4 +338,24 @@ class EmailLexer extends AbstractLexer
{
return 'iu';
}
public function getAccumulatedValues() : string
{
return $this->accumulator;
}
public function startRecording() : void
{
$this->hasToRecord = true;
}
public function stopRecording() : void
{
$this->hasToRecord = false;
}
public function clearRecorded() : void
{
$this->accumulator = '';
}
}

View File

@ -2,26 +2,19 @@
namespace Egulias\EmailValidator;
use Egulias\EmailValidator\Exception\ExpectingATEXT;
use Egulias\EmailValidator\Exception\NoLocalPart;
use Egulias\EmailValidator\Parser\DomainPart;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Result\Result;
use Egulias\EmailValidator\Parser\LocalPart;
use Egulias\EmailValidator\Parser\DomainPart;
use Egulias\EmailValidator\Result\ValidEmail;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Warning\EmailTooLong;
use Egulias\EmailValidator\Result\Reason\NoLocalPart;
/**
* EmailParser
*
* @author Eduardo Gulias Davis <me@egulias.com>
*/
class EmailParser
class EmailParser extends Parser
{
const EMAIL_MAX_LENGTH = 254;
/**
* @var array
*/
protected $warnings = [];
/**
* @var string
*/
@ -31,104 +24,65 @@ class EmailParser
* @var string
*/
protected $localPart = '';
/**
* @var EmailLexer
*/
protected $lexer;
/**
* @var LocalPart
*/
protected $localPartParser;
/**
* @var DomainPart
*/
protected $domainPartParser;
public function __construct(EmailLexer $lexer)
public function parse(string $str) : Result
{
$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);
$result = parent::parse($str);
$this->addLongEmailWarning($this->localPart, $this->domainPart);
return $this->warnings;
return $result;
}
protected function preLeftParsing(): Result
{
if (!$this->hasAtToken()) {
return new InvalidEmail(new NoLocalPart(), $this->lexer->token["value"]);
}
return new ValidEmail();
}
/**
* @return string
*/
public function getParsedDomainPart()
protected function parseLeftFromAt(): Result
{
return $this->processLocalPart();
}
protected function parseRightFromAt(): Result
{
return $this->processDomainPart();
}
private function processLocalPart() : Result
{
$localPartParser = new LocalPart($this->lexer);
$localPartResult = $localPartParser->parse();
$this->localPart = $localPartParser->localPart();
$this->warnings = array_merge($localPartParser->getWarnings(), $this->warnings);
return $localPartResult;
}
private function processDomainPart() : Result
{
$domainPartParser = new DomainPart($this->lexer);
$domainPartResult = $domainPartParser->parse();
$this->domainPart = $domainPartParser->domainPart();
$this->warnings = array_merge($domainPartParser->getWarnings(), $this->warnings);
return $domainPartResult;
}
public function getDomainPart() : string
{
return $this->domainPart;
}
/**
* @param string $email
*/
protected function setParts($email)
public function getLocalPart() : string
{
$parts = explode('@', $email);
$this->domainPart = $this->domainPartParser->getDomainPart();
$this->localPart = $parts[0];
return $this->localPart;
}
/**
* @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)
private function addLongEmailWarning(string $localPart, string $parsedDomainPart) : void
{
if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAIL_MAX_LENGTH) {
$this->warnings[EmailTooLong::CODE] = new EmailTooLong();

View File

@ -2,7 +2,7 @@
namespace Egulias\EmailValidator;
use Egulias\EmailValidator\Exception\InvalidEmail;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Validation\EmailValidation;
class EmailValidator
@ -15,12 +15,12 @@ class EmailValidator
/**
* @var Warning\Warning[]
*/
protected $warnings = [];
private $warnings = [];
/**
* @var InvalidEmail|null
* @var ?InvalidEmail
*/
protected $error;
private $error;
public function __construct()
{
@ -32,7 +32,7 @@ class EmailValidator
* @param EmailValidation $emailValidation
* @return bool
*/
public function isValid($email, EmailValidation $emailValidation)
public function isValid(string $email, EmailValidation $emailValidation)
{
$isValid = $emailValidation->isValid($email, $this->lexer);
$this->warnings = $emailValidation->getWarnings();

View File

@ -3,417 +3,310 @@
namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Exception\CharNotAllowed;
use Egulias\EmailValidator\Exception\CommaInDomain;
use Egulias\EmailValidator\Exception\ConsecutiveAt;
use Egulias\EmailValidator\Exception\CRLFAtTheEnd;
use Egulias\EmailValidator\Exception\CRNoLF;
use Egulias\EmailValidator\Exception\DomainHyphened;
use Egulias\EmailValidator\Exception\DotAtEnd;
use Egulias\EmailValidator\Exception\DotAtStart;
use Egulias\EmailValidator\Exception\ExpectingATEXT;
use Egulias\EmailValidator\Exception\ExpectingDomainLiteralClose;
use Egulias\EmailValidator\Exception\ExpectingDTEXT;
use Egulias\EmailValidator\Exception\NoDomainPart;
use Egulias\EmailValidator\Exception\UnopenedComment;
use Egulias\EmailValidator\Warning\AddressLiteral;
use Egulias\EmailValidator\Warning\CFWSWithFWS;
use Egulias\EmailValidator\Warning\DeprecatedComment;
use Egulias\EmailValidator\Warning\DomainLiteral;
use Egulias\EmailValidator\Warning\DomainTooLong;
use Egulias\EmailValidator\Warning\IPV6BadChar;
use Egulias\EmailValidator\Warning\IPV6ColonEnd;
use Egulias\EmailValidator\Warning\IPV6ColonStart;
use Egulias\EmailValidator\Warning\IPV6Deprecated;
use Egulias\EmailValidator\Warning\IPV6DoubleColon;
use Egulias\EmailValidator\Warning\IPV6GroupCount;
use Egulias\EmailValidator\Warning\IPV6MaxGroups;
use Egulias\EmailValidator\Warning\LabelTooLong;
use Egulias\EmailValidator\Warning\ObsoleteDTEXT;
use Egulias\EmailValidator\Warning\TLD;
use Egulias\EmailValidator\Result\Result;
use Egulias\EmailValidator\Result\ValidEmail;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Result\Reason\DotAtEnd;
use Egulias\EmailValidator\Result\Reason\DotAtStart;
use Egulias\EmailValidator\Warning\DeprecatedComment;
use Egulias\EmailValidator\Result\Reason\CRLFAtTheEnd;
use Egulias\EmailValidator\Result\Reason\LabelTooLong;
use Egulias\EmailValidator\Result\Reason\NoDomainPart;
use Egulias\EmailValidator\Result\Reason\ConsecutiveAt;
use Egulias\EmailValidator\Result\Reason\DomainTooLong;
use Egulias\EmailValidator\Result\Reason\CharNotAllowed;
use Egulias\EmailValidator\Result\Reason\DomainHyphened;
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
use Egulias\EmailValidator\Parser\CommentStrategy\DomainComment;
use Egulias\EmailValidator\Result\Reason\ExpectingDomainLiteralClose;
use Egulias\EmailValidator\Parser\DomainLiteral as DomainLiteralParser;
class DomainPart extends Parser
class DomainPart extends PartParser
{
const DOMAIN_MAX_LENGTH = 254;
const DOMAIN_MAX_LENGTH = 253;
const LABEL_MAX_LENGTH = 63;
/**
* @var string
*/
protected $domainPart = '';
public function parse($domainPart)
/**
* @var string
*/
protected $label = '';
public function parse() : Result
{
$this->lexer->clearRecorded();
$this->lexer->startRecording();
$this->lexer->moveNext();
$this->performDomainStartChecks();
$domain = $this->doParseDomainPart();
$prev = $this->lexer->getPrevious();
$length = strlen($domain);
if ($prev['type'] === EmailLexer::S_DOT) {
throw new DotAtEnd();
$domainChecks = $this->performDomainStartChecks();
if ($domainChecks->isInvalid()) {
return $domainChecks;
}
if ($prev['type'] === EmailLexer::S_HYPHEN) {
throw new DomainHyphened();
if ($this->lexer->token['type'] === EmailLexer::S_AT) {
return new InvalidEmail(new ConsecutiveAt(), $this->lexer->token['value']);
}
$result = $this->doParseDomainPart();
if ($result->isInvalid()) {
return $result;
}
$end = $this->checkEndOfDomain();
if ($end->isInvalid()) {
return $end;
}
$this->lexer->stopRecording();
$this->domainPart = $this->lexer->getAccumulatedValues();
$length = strlen($this->domainPart);
if ($length > self::DOMAIN_MAX_LENGTH) {
$this->warnings[DomainTooLong::CODE] = new DomainTooLong();
return new InvalidEmail(new DomainTooLong(), $this->lexer->token['value']);
}
if ($prev['type'] === EmailLexer::S_CR) {
throw new CRLFAtTheEnd();
}
$this->domainPart = $domain;
return new ValidEmail();
}
private function performDomainStartChecks()
private function checkEndOfDomain() : Result
{
$this->checkInvalidTokensAfterAT();
$this->checkEmptyDomain();
$prev = $this->lexer->getPrevious();
if ($prev['type'] === EmailLexer::S_DOT) {
return new InvalidEmail(new DotAtEnd(), $this->lexer->token['value']);
}
if ($prev['type'] === EmailLexer::S_HYPHEN) {
return new InvalidEmail(new DomainHyphened('Hypen found at the end of the domain'), $prev['value']);
}
if ($this->lexer->token['type'] === EmailLexer::S_SP) {
return new InvalidEmail(new CRLFAtTheEnd(), $prev['value']);
}
return new ValidEmail();
}
private function performDomainStartChecks() : Result
{
$invalidTokens = $this->checkInvalidTokensAfterAT();
if ($invalidTokens->isInvalid()) {
return $invalidTokens;
}
$missingDomain = $this->checkEmptyDomain();
if ($missingDomain->isInvalid()) {
return $missingDomain;
}
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->warnings[DeprecatedComment::CODE] = new DeprecatedComment();
$this->parseDomainComments();
}
return new ValidEmail();
}
private function checkEmptyDomain()
private function checkEmptyDomain() : Result
{
$thereIsNoDomain = $this->lexer->token['type'] === EmailLexer::S_EMPTY ||
($this->lexer->token['type'] === EmailLexer::S_SP &&
!$this->lexer->isNextToken(EmailLexer::GENERIC));
if ($thereIsNoDomain) {
throw new NoDomainPart();
return new InvalidEmail(new NoDomainPart(), $this->lexer->token['value']);
}
return new ValidEmail();
}
private function checkInvalidTokensAfterAT()
private function checkInvalidTokensAfterAT() : Result
{
if ($this->lexer->token['type'] === EmailLexer::S_DOT) {
throw new DotAtStart();
return new InvalidEmail(new DotAtStart(), $this->lexer->token['value']);
}
if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) {
throw new DomainHyphened();
return new InvalidEmail(new DomainHyphened('After AT'), $this->lexer->token['value']);
}
return new ValidEmail();
}
/**
* @return string
*/
public function getDomainPart()
protected function parseComments(): Result
{
return $this->domainPart;
$commentParser = new Comment($this->lexer, new DomainComment());
$result = $commentParser->parse();
$this->warnings = array_merge($this->warnings, $commentParser->getWarnings());
return $result;
}
/**
* @param string $addressLiteral
* @param int $maxGroups
*/
public function checkIPV6Tag($addressLiteral, $maxGroups = 8)
{
$prev = $this->lexer->getPrevious();
if ($prev['type'] === EmailLexer::S_COLON) {
$this->warnings[IPV6ColonEnd::CODE] = new IPV6ColonEnd();
}
$IPv6 = substr($addressLiteral, 5);
//Daniel Marschall's new IPv6 testing strategy
$matchesIP = explode(':', $IPv6);
$groupCount = count($matchesIP);
$colons = strpos($IPv6, '::');
if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) {
$this->warnings[IPV6BadChar::CODE] = new IPV6BadChar();
}
if ($colons === false) {
// We need exactly the right number of groups
if ($groupCount !== $maxGroups) {
$this->warnings[IPV6GroupCount::CODE] = new IPV6GroupCount();
}
return;
}
if ($colons !== strrpos($IPv6, '::')) {
$this->warnings[IPV6DoubleColon::CODE] = new IPV6DoubleColon();
return;
}
if ($colons === 0 || $colons === (strlen($IPv6) - 2)) {
// RFC 4291 allows :: at the start or end of an address
//with 7 other groups in addition
++$maxGroups;
}
if ($groupCount > $maxGroups) {
$this->warnings[IPV6MaxGroups::CODE] = new IPV6MaxGroups();
} elseif ($groupCount === $maxGroups) {
$this->warnings[IPV6Deprecated::CODE] = new IPV6Deprecated();
}
}
/**
* @return string
*/
protected function doParseDomainPart()
protected function doParseDomainPart() : Result
{
$tldMissing = true;
$hasComments = false;
$domain = '';
$openedParenthesis = 0;
do {
$prev = $this->lexer->getPrevious();
$this->checkNotAllowedChars($this->lexer->token);
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->parseComments();
$openedParenthesis += $this->getOpenedParenthesis();
$this->lexer->moveNext();
$tmpPrev = $this->lexer->getPrevious();
if ($tmpPrev['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
$openedParenthesis--;
}
$notAllowedChars = $this->checkNotAllowedChars($this->lexer->token);
if ($notAllowedChars->isInvalid()) {
return $notAllowedChars;
}
if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
if ($openedParenthesis === 0) {
throw new UnopenedComment();
} else {
$openedParenthesis--;
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS ||
$this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS ) {
$hasComments = true;
$commentsResult = $this->parseComments();
//Invalid comment parsing
if($commentsResult->isInvalid()) {
return $commentsResult;
}
}
$this->checkConsecutiveDots();
$this->checkDomainPartExceptions($prev);
if ($this->hasBrackets()) {
$this->parseDomainLiteral();
$dotsResult = $this->checkConsecutiveDots();
if ($dotsResult->isInvalid()) {
return $dotsResult;
}
$this->checkLabelLength($prev);
if ($this->lexer->token['type'] === EmailLexer::S_OPENBRACKET) {
$literalResult = $this->parseDomainLiteral();
if ($this->isFWS()) {
$this->parseFWS();
$this->addTLDWarnings($tldMissing);
return $literalResult;
}
$labelCheck = $this->checkLabelLength();
if ($labelCheck->isInvalid()) {
return $labelCheck;
}
$FwsResult = $this->parseFWS();
if($FwsResult->isInvalid()) {
return $FwsResult;
}
$domain .= $this->lexer->token['value'];
$this->lexer->moveNext();
if ($this->lexer->token['type'] === EmailLexer::S_SP) {
throw new CharNotAllowed();
if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::GENERIC)) {
$tldMissing = false;
}
$exceptionsResult = $this->checkDomainPartExceptions($prev, $hasComments);
if ($exceptionsResult->isInvalid()) {
return $exceptionsResult;
}
$this->lexer->moveNext();
} while (null !== $this->lexer->token['type']);
return $domain;
$labelCheck = $this->checkLabelLength(true);
if ($labelCheck->isInvalid()) {
return $labelCheck;
}
$this->addTLDWarnings($tldMissing);
$this->domainPart = $domain;
return new ValidEmail();
}
private function checkNotAllowedChars(array $token)
private function checkNotAllowedChars(array $token) : Result
{
$notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true];
if (isset($notAllowed[$token['type']])) {
throw new CharNotAllowed();
return new InvalidEmail(new CharNotAllowed(), $token['value']);
}
return new ValidEmail();
}
/**
* @return string|false
* @return Result
*/
protected function parseDomainLiteral()
protected function parseDomainLiteral() : Result
{
if ($this->lexer->isNextToken(EmailLexer::S_COLON)) {
$this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
}
if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) {
$lexer = clone $this->lexer;
$lexer->moveNext();
if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) {
$this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
}
try {
$this->lexer->find(EmailLexer::S_CLOSEBRACKET);
} catch (\RuntimeException $e) {
return new InvalidEmail(new ExpectingDomainLiteralClose(), $this->lexer->token['value']);
}
return $this->doParseDomainLiteral();
$domainLiteralParser = new DomainLiteralParser($this->lexer);
$result = $domainLiteralParser->parse();
$this->warnings = array_merge($this->warnings, $domainLiteralParser->getWarnings());
return $result;
}
/**
* @return string|false
*/
protected function doParseDomainLiteral()
protected function checkDomainPartExceptions(array $prev, bool $hasComments) : Result
{
$IPv6TAG = false;
$addressLiteral = '';
do {
if ($this->lexer->token['type'] === EmailLexer::C_NUL) {
throw new ExpectingDTEXT();
}
if ($this->lexer->token['type'] === EmailLexer::INVALID ||
$this->lexer->token['type'] === EmailLexer::C_DEL ||
$this->lexer->token['type'] === EmailLexer::S_LF
) {
$this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
}
if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) {
throw new ExpectingDTEXT();
}
if ($this->lexer->isNextTokenAny(
array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF)
)) {
$this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
$this->parseFWS();
}
if ($this->lexer->isNextToken(EmailLexer::S_CR)) {
throw new CRNoLF();
}
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) {
$this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
$addressLiteral .= $this->lexer->token['value'];
$this->lexer->moveNext();
$this->validateQuotedPair();
}
if ($this->lexer->token['type'] === EmailLexer::S_IPV6TAG) {
$IPv6TAG = true;
}
if ($this->lexer->token['type'] === EmailLexer::S_CLOSEQBRACKET) {
break;
}
$addressLiteral .= $this->lexer->token['value'];
} while ($this->lexer->moveNext());
$addressLiteral = str_replace('[', '', $addressLiteral);
$addressLiteral = $this->checkIPV4Tag($addressLiteral);
if (false === $addressLiteral) {
return $addressLiteral;
}
if (!$IPv6TAG) {
$this->warnings[DomainLiteral::CODE] = new DomainLiteral();
return $addressLiteral;
}
$this->warnings[AddressLiteral::CODE] = new AddressLiteral();
$this->checkIPV6Tag($addressLiteral);
return $addressLiteral;
}
/**
* @param string $addressLiteral
*
* @return string|false
*/
protected function checkIPV4Tag($addressLiteral)
{
$matchesIP = array();
// Extract IPv4 part from the end of the address-literal (if there is one)
if (preg_match(
'/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/',
$addressLiteral,
$matchesIP
) > 0
) {
$index = strrpos($addressLiteral, $matchesIP[0]);
if ($index === 0) {
$this->warnings[AddressLiteral::CODE] = new AddressLiteral();
return false;
}
// Convert IPv4 part to IPv6 format for further testing
$addressLiteral = substr($addressLiteral, 0, (int) $index) . '0:0';
}
return $addressLiteral;
}
protected function checkDomainPartExceptions(array $prev)
{
$invalidDomainTokens = array(
EmailLexer::S_DQUOTE => true,
EmailLexer::S_SQUOTE => true,
EmailLexer::S_BACKTICK => true,
EmailLexer::S_SEMICOLON => true,
EmailLexer::S_GREATERTHAN => true,
EmailLexer::S_LOWERTHAN => true,
);
if (isset($invalidDomainTokens[$this->lexer->token['type']])) {
throw new ExpectingATEXT();
}
if ($this->lexer->token['type'] === EmailLexer::S_COMMA) {
throw new CommaInDomain();
}
if ($this->lexer->token['type'] === EmailLexer::S_AT) {
throw new ConsecutiveAt();
}
if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) {
throw new ExpectingATEXT();
if ($this->lexer->token['type'] === EmailLexer::S_OPENBRACKET && $prev['type'] !== EmailLexer::S_AT) {
return new InvalidEmail(new ExpectingATEXT('OPENBRACKET not after AT'), $this->lexer->token['value']);
}
if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
throw new DomainHyphened();
return new InvalidEmail(new DomainHyphened('Hypen found near DOT'), $this->lexer->token['value']);
}
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH
&& $this->lexer->isNextToken(EmailLexer::GENERIC)) {
throw new ExpectingATEXT();
return new InvalidEmail(new ExpectingATEXT('Escaping following "ATOM"'), $this->lexer->token['value']);
}
return $this->validateTokens($hasComments);
}
/**
* @return bool
*/
protected function hasBrackets()
protected function validateTokens(bool $hasComments) : Result
{
if ($this->lexer->token['type'] !== EmailLexer::S_OPENBRACKET) {
return false;
$validDomainTokens = array(
EmailLexer::GENERIC => true,
EmailLexer::S_HYPHEN => true,
EmailLexer::S_DOT => true,
);
if ($hasComments) {
$validDomainTokens[EmailLexer::S_OPENPARENTHESIS] = true;
$validDomainTokens[EmailLexer::S_CLOSEPARENTHESIS] = true;
}
try {
$this->lexer->find(EmailLexer::S_CLOSEBRACKET);
} catch (\RuntimeException $e) {
throw new ExpectingDomainLiteralClose();
if (!isset($validDomainTokens[$this->lexer->token['type']])) {
return new InvalidEmail(new ExpectingATEXT('Invalid token in domain: ' . $this->lexer->token['value']), $this->lexer->token['value']);
}
return true;
return new ValidEmail();
}
protected function checkLabelLength(array $prev)
private function checkLabelLength(bool $isEndOfDomain = false) : Result
{
if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
$prev['type'] === EmailLexer::GENERIC &&
strlen($prev['value']) > 63
) {
$this->warnings[LabelTooLong::CODE] = new LabelTooLong();
if ($this->lexer->token['type'] === EmailLexer::S_DOT || $isEndOfDomain) {
if ($this->isLabelTooLong($this->label)) {
return new InvalidEmail(new LabelTooLong(), $this->lexer->token['value']);
}
$this->label = '';
}
$this->label .= $this->lexer->token['value'];
return new ValidEmail();
}
protected function parseDomainComments()
{
$this->isUnclosedComment();
while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
$this->warnEscaping();
$this->lexer->moveNext();
}
$this->lexer->moveNext();
if ($this->lexer->isNextToken(EmailLexer::S_DOT)) {
throw new ExpectingATEXT();
private function isLabelTooLong(string $label) : bool
{
if (preg_match('/[^\x00-\x7F]/', $label)) {
idn_to_ascii(utf8_decode($label), IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
return (bool) ($idnaInfo['errors'] & IDNA_ERROR_LABEL_TOO_LONG);
}
return strlen($label) > self::LABEL_MAX_LENGTH;
}
protected function addTLDWarnings()
private function addTLDWarnings(bool $isTLDMissing) : void
{
if ($this->warnings[DomainLiteral::CODE]) {
if ($isTLDMissing) {
$this->warnings[TLD::CODE] = new TLD();
}
}
}
public function domainPart() : string
{
return $this->domainPart;
}
}

View File

@ -2,144 +2,163 @@
namespace Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\Exception\DotAtEnd;
use Egulias\EmailValidator\Exception\DotAtStart;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Exception\ExpectingAT;
use Egulias\EmailValidator\Exception\ExpectingATEXT;
use Egulias\EmailValidator\Exception\UnclosedQuotedString;
use Egulias\EmailValidator\Exception\UnopenedComment;
use Egulias\EmailValidator\Warning\CFWSWithFWS;
use Egulias\EmailValidator\Result\Result;
use Egulias\EmailValidator\Result\ValidEmail;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Warning\LocalTooLong;
use Egulias\EmailValidator\Result\Reason\DotAtEnd;
use Egulias\EmailValidator\Result\Reason\DotAtStart;
use Egulias\EmailValidator\Result\Reason\ConsecutiveDot;
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
use Egulias\EmailValidator\Parser\CommentStrategy\LocalComment;
class LocalPart extends Parser
class LocalPart extends PartParser
{
public function parse($localPart)
/**
* @var string
*/
private $localPart = '';
public function parse() : Result
{
$parseDQuote = true;
$closingQuote = false;
$openedParenthesis = 0;
$totalLength = 0;
$this->lexer->startRecording();
while ($this->lexer->token['type'] !== EmailLexer::S_AT && null !== $this->lexer->token['type']) {
if ($this->lexer->token['type'] === EmailLexer::S_DOT && null === $this->lexer->getPrevious()['type']) {
throw new DotAtStart();
if ($this->hasDotAtStart()) {
return new InvalidEmail(new DotAtStart(), $this->lexer->token['value']);
}
$closingQuote = $this->checkDQUOTE($closingQuote);
if ($closingQuote && $parseDQuote) {
$parseDQuote = $this->parseDoubleQuote();
}
if ($this->lexer->token['type'] === EmailLexer::S_DQUOTE) {
$dquoteParsingResult = $this->parseDoubleQuote();
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
$this->parseComments();
$openedParenthesis += $this->getOpenedParenthesis();
}
if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
if ($openedParenthesis === 0) {
throw new UnopenedComment();
//Invalid double quote parsing
if($dquoteParsingResult->isInvalid()) {
return $dquoteParsingResult;
}
$openedParenthesis--;
}
$this->checkConsecutiveDots();
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS ||
$this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS ) {
$commentsResult = $this->parseComments();
//Invalid comment parsing
if($commentsResult->isInvalid()) {
return $commentsResult;
}
}
if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
return new InvalidEmail(new ConsecutiveDot(), $this->lexer->token['value']);
}
if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
$this->lexer->isNextToken(EmailLexer::S_AT)
) {
throw new DotAtEnd();
return new InvalidEmail(new DotAtEnd(), $this->lexer->token['value']);
}
$this->warnEscaping();
$this->isInvalidToken($this->lexer->token, $closingQuote);
if ($this->isFWS()) {
$this->parseFWS();
$resultEscaping = $this->validateEscaping();
if ($resultEscaping->isInvalid()) {
return $resultEscaping;
}
$resultToken = $this->validateTokens(false);
if ($resultToken->isInvalid()) {
return $resultToken;
}
$resultFWS = $this->parseLocalFWS();
if($resultFWS->isInvalid()) {
return $resultFWS;
}
$totalLength += strlen($this->lexer->token['value']);
$this->lexer->moveNext();
}
if ($totalLength > LocalTooLong::LOCAL_PART_LENGTH) {
$this->lexer->stopRecording();
$this->localPart = rtrim($this->lexer->getAccumulatedValues(), '@');
if (strlen($this->localPart) > LocalTooLong::LOCAL_PART_LENGTH) {
$this->warnings[LocalTooLong::CODE] = new LocalTooLong();
}
return new ValidEmail();
}
/**
* @return bool
*/
protected function parseDoubleQuote()
protected function validateTokens(bool $hasComments) : Result
{
$parseAgain = true;
$special = array(
EmailLexer::S_CR => true,
EmailLexer::S_HTAB => true,
EmailLexer::S_LF => true
$invalidTokens = array(
EmailLexer::S_COMMA => EmailLexer::S_COMMA,
EmailLexer::S_CLOSEBRACKET => EmailLexer::S_CLOSEBRACKET,
EmailLexer::S_OPENBRACKET => EmailLexer::S_OPENBRACKET,
EmailLexer::S_GREATERTHAN => EmailLexer::S_GREATERTHAN,
EmailLexer::S_LOWERTHAN => EmailLexer::S_LOWERTHAN,
EmailLexer::S_COLON => EmailLexer::S_COLON,
EmailLexer::S_SEMICOLON => EmailLexer::S_SEMICOLON,
EmailLexer::INVALID => EmailLexer::INVALID
);
$invalid = array(
EmailLexer::C_NUL => true,
EmailLexer::S_HTAB => true,
EmailLexer::S_CR => true,
EmailLexer::S_LF => true
);
$setSpecialsWarning = true;
$this->lexer->moveNext();
while ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE && null !== $this->lexer->token['type']) {
$parseAgain = false;
if (isset($special[$this->lexer->token['type']]) && $setSpecialsWarning) {
$this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
$setSpecialsWarning = false;
}
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH && $this->lexer->isNextToken(EmailLexer::S_DQUOTE)) {
$this->lexer->moveNext();
}
$this->lexer->moveNext();
if (!$this->escaped() && isset($invalid[$this->lexer->token['type']])) {
throw new ExpectingATEXT();
}
if (isset($invalidTokens[$this->lexer->token['type']])) {
return new InvalidEmail(new ExpectingATEXT('Invalid token found'), $this->lexer->token['value']);
}
return new ValidEmail();
}
$prev = $this->lexer->getPrevious();
public function localPart() : string
{
return $this->localPart;
}
if ($prev['type'] === EmailLexer::S_BACKSLASH) {
if (!$this->checkDQUOTE(false)) {
throw new UnclosedQuotedString();
}
private function parseLocalFWS() : Result
{
$foldingWS = new FoldingWhiteSpace($this->lexer);
$resultFWS = $foldingWS->parse();
if ($resultFWS->isValid()) {
$this->warnings = array_merge($this->warnings, $foldingWS->getWarnings());
}
return $resultFWS;
}
if (!$this->lexer->isNextToken(EmailLexer::S_AT) && $prev['type'] !== EmailLexer::S_BACKSLASH) {
throw new ExpectingAT();
}
private function hasDotAtStart() : bool
{
return $this->lexer->token['type'] === EmailLexer::S_DOT && null === $this->lexer->getPrevious()['type'];
}
private function parseDoubleQuote() : Result
{
$dquoteParser = new DoubleQuote($this->lexer);
$parseAgain = $dquoteParser->parse();
$this->warnings = array_merge($this->warnings, $dquoteParser->getWarnings());
return $parseAgain;
}
/**
* @param bool $closingQuote
*/
protected function isInvalidToken(array $token, $closingQuote)
protected function parseComments(): Result
{
$forbidden = array(
EmailLexer::S_COMMA,
EmailLexer::S_CLOSEBRACKET,
EmailLexer::S_OPENBRACKET,
EmailLexer::S_GREATERTHAN,
EmailLexer::S_LOWERTHAN,
EmailLexer::S_COLON,
EmailLexer::S_SEMICOLON,
EmailLexer::INVALID
);
if (in_array($token['type'], $forbidden) && !$closingQuote) {
throw new ExpectingATEXT();
$commentParser = new Comment($this->lexer, new LocalComment());
$result = $commentParser->parse();
$this->warnings = array_merge($this->warnings, $commentParser->getWarnings());
if($result->isInvalid()) {
return $result;
}
return $result;
}
}
private function validateEscaping() : Result
{
//Backslash found
if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) {
return new ValidEmail();
}
if ($this->lexer->isNextToken(EmailLexer::GENERIC)) {
return new InvalidEmail(new ExpectingATEXT('Found ATOM after escaping'), $this->lexer->token['value']);
}
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) {
return new ValidEmail();
}
return new ValidEmail();
}
}

View File

@ -3,14 +3,20 @@
namespace Egulias\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Exception\InvalidEmail;
use Egulias\EmailValidator\Exception\LocalOrReservedDomain;
use Egulias\EmailValidator\Exception\DomainAcceptsNoMail;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Result\Reason\DomainAcceptsNoMail;
use Egulias\EmailValidator\Result\Reason\LocalOrReservedDomain;
use Egulias\EmailValidator\Result\Reason\NoDNSRecord as ReasonNoDNSRecord;
use Egulias\EmailValidator\Result\Reason\UnableToGetDNSRecord;
use Egulias\EmailValidator\Warning\NoDNSMXRecord;
use Egulias\EmailValidator\Exception\NoDNSRecord;
class DNSCheckValidation implements EmailValidation
{
/**
* @var int
*/
protected const DNS_RECORD_TYPES_TO_CHECK = DNS_MX + DNS_A + DNS_AAAA;
/**
* @var array
*/
@ -34,7 +40,7 @@ class DNSCheckValidation implements EmailValidation
}
}
public function isValid($email, EmailLexer $emailLexer)
public function isValid(string $email, EmailLexer $emailLexer) : bool
{
// use the input to check DNS if we cannot extract something similar to a domain
$host = $email;
@ -73,19 +79,19 @@ class DNSCheckValidation implements EmailValidation
// Exclude reserved top level DNS names
if ($isLocalDomain || $isReservedTopLevel) {
$this->error = new LocalOrReservedDomain();
$this->error = new InvalidEmail(new LocalOrReservedDomain(), $host);
return false;
}
return $this->checkDns($host);
}
public function getError()
public function getError() : ?InvalidEmail
{
return $this->error;
}
public function getWarnings()
public function getWarnings() : array
{
return $this->warnings;
}
@ -112,31 +118,43 @@ class DNSCheckValidation implements EmailValidation
*
* @return bool True on success.
*/
private function validateDnsRecords($host)
private function validateDnsRecords($host) : bool
{
// Get all MX, A and AAAA DNS records for host
// Using @ as workaround to fix https://bugs.php.net/bug.php?id=73149
$dnsRecords = @dns_get_record($host, DNS_MX + DNS_A + DNS_AAAA);
// A workaround to fix https://bugs.php.net/bug.php?id=73149
/** @psalm-suppress InvalidArgument */
set_error_handler(
static function (int $errorLevel, string $errorMessage): ?bool {
throw new \RuntimeException("Unable to get DNS record for the host: $errorMessage");
}
);
try {
// Get all MX, A and AAAA DNS records for host
$dnsRecords = dns_get_record($host, static::DNS_RECORD_TYPES_TO_CHECK);
} catch (\RuntimeException $exception) {
$this->error = new InvalidEmail(new UnableToGetDNSRecord(), '');
return false;
} finally {
restore_error_handler();
}
// No MX, A or AAAA DNS records
if (empty($dnsRecords)) {
$this->error = new NoDNSRecord();
if ($dnsRecords === [] || $dnsRecords === false) {
$this->error = new InvalidEmail(new ReasonNoDNSRecord(), '');
return false;
}
// For each DNS record
foreach ($dnsRecords as $dnsRecord) {
if (!$this->validateMXRecord($dnsRecord)) {
// No MX records (fallback to A or AAAA records)
if (empty($this->mxRecords)) {
$this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord();
}
return false;
}
}
// No MX records (fallback to A or AAAA records)
if (empty($this->mxRecords)) {
$this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord();
}
return true;
}
@ -147,7 +165,7 @@ class DNSCheckValidation implements EmailValidation
*
* @return bool True if valid.
*/
private function validateMxRecord($dnsRecord)
private function validateMxRecord($dnsRecord) : bool
{
if ($dnsRecord['type'] !== 'MX') {
return true;
@ -155,7 +173,7 @@ class DNSCheckValidation implements EmailValidation
// "Null MX" record indicates the domain accepts no mail (https://tools.ietf.org/html/rfc7505)
if (empty($dnsRecord['target']) || $dnsRecord['target'] === '.') {
$this->error = new DomainAcceptsNoMail();
$this->error = new InvalidEmail(new DomainAcceptsNoMail(), "");
return false;
}
@ -163,4 +181,4 @@ class DNSCheckValidation implements EmailValidation
return true;
}
}
}

View File

@ -3,7 +3,7 @@
namespace Egulias\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Exception\InvalidEmail;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Warning\Warning;
interface EmailValidation
@ -16,19 +16,19 @@ interface EmailValidation
*
* @return bool
*/
public function isValid($email, EmailLexer $emailLexer);
public function isValid(string $email, EmailLexer $emailLexer) : bool;
/**
* Returns the validation error.
*
* @return InvalidEmail|null
*/
public function getError();
public function getError() : ?InvalidEmail;
/**
* Returns the validation warnings.
*
* @return Warning[]
*/
public function getWarnings();
public function getWarnings() : array;
}

View File

@ -3,13 +3,15 @@
namespace Egulias\EmailValidator\Validation;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Validation\Exception\EmptyValidationList;
use Egulias\EmailValidator\Result\MultipleErrors;
class MultipleValidationWithAnd implements EmailValidation
{
/**
* If one of validations gets failure skips all succeeding validation.
* This means MultipleErrors will only contain a single error which first found.
* If one of validations fails, the remaining validations will be skept.
* This means MultipleErrors will only contain a single error, the first found.
*/
const STOP_ON_ERROR = 0;
@ -56,60 +58,51 @@ class MultipleValidationWithAnd implements EmailValidation
/**
* {@inheritdoc}
*/
public function isValid($email, EmailLexer $emailLexer)
public function isValid(string $email, EmailLexer $emailLexer) : bool
{
$result = true;
$errors = [];
foreach ($this->validations as $validation) {
$emailLexer->reset();
$validationResult = $validation->isValid($email, $emailLexer);
$result = $result && $validationResult;
$this->warnings = array_merge($this->warnings, $validation->getWarnings());
$errors = $this->addNewError($validation->getError(), $errors);
if (!$validationResult) {
$this->processError($validation);
}
if ($this->shouldStop($result)) {
break;
}
}
if (!empty($errors)) {
$this->error = new MultipleErrors($errors);
}
return $result;
}
/**
* @param \Egulias\EmailValidator\Exception\InvalidEmail|null $possibleError
* @param \Egulias\EmailValidator\Exception\InvalidEmail[] $errors
*
* @return \Egulias\EmailValidator\Exception\InvalidEmail[]
*/
private function addNewError($possibleError, array $errors)
private function initErrorStorage() : void
{
if (null !== $possibleError) {
$errors[] = $possibleError;
if (null === $this->error) {
$this->error = new MultipleErrors();
}
return $errors;
}
/**
* @param bool $result
*
* @return bool
*/
private function shouldStop($result)
private function processError(EmailValidation $validation) : void
{
if (null !== $validation->getError()) {
$this->initErrorStorage();
/** @psalm-suppress PossiblyNullReference */
$this->error->addReason($validation->getError()->reason());
}
}
private function shouldStop(bool $result) : bool
{
return !$result && $this->mode === self::STOP_ON_ERROR;
}
/**
* Returns the validation errors.
*
* @return MultipleErrors|null
*/
public function getError()
public function getError() : ?InvalidEmail
{
return $this->error;
}
@ -117,7 +110,7 @@ class MultipleValidationWithAnd implements EmailValidation
/**
* {@inheritdoc}
*/
public function getWarnings()
public function getWarnings() : array
{
return $this->warnings;
}

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