symfony/lock 추가
This commit is contained in:
+2
-1
@@ -16,7 +16,8 @@
|
||||
"firephp/firephp-core": "^0.4.0",
|
||||
"vlucas/valitron": "^1.4",
|
||||
"php-extended/php-tail": "^2.1",
|
||||
"pguardiario/phpuri": "^1.0"
|
||||
"pguardiario/phpuri": "^1.0",
|
||||
"symfony/lock": "^3.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
||||
Generated
+170
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "1753c2c2ad9fe40ecc5c6ae541dd2e78",
|
||||
"content-hash": "a329d39be5161c87438d41f9adf4417b",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brandonwamboldt/utilphp",
|
||||
@@ -454,6 +454,54 @@
|
||||
],
|
||||
"time": "2017-06-19T01:22:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v2.0.12",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/random_compat.git",
|
||||
"reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb",
|
||||
"reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*|5.*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/random.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
||||
"keywords": [
|
||||
"csprng",
|
||||
"pseudorandom",
|
||||
"random"
|
||||
],
|
||||
"time": "2018-04-04T21:24:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pguardiario/phpuri",
|
||||
"version": "1.0",
|
||||
@@ -702,6 +750,127 @@
|
||||
],
|
||||
"time": "2014-06-16T22:40:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/lock",
|
||||
"version": "v3.4.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/lock.git",
|
||||
"reference": "3a707193326d31603824706e34a95af899a59788"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/lock/zipball/3a707193326d31603824706e34a95af899a59788",
|
||||
"reference": "3a707193326d31603824706e34a95af899a59788",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.5.9|>=7.0.8",
|
||||
"psr/log": "~1.0",
|
||||
"symfony/polyfill-php70": "~1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"predis/predis": "~1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.4-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Lock\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
},
|
||||
{
|
||||
"name": "Jérémy Derussé",
|
||||
"email": "jeremy@derusse.com"
|
||||
}
|
||||
],
|
||||
"description": "Symfony Lock Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"cas",
|
||||
"flock",
|
||||
"locking",
|
||||
"mutex",
|
||||
"redlock",
|
||||
"semaphore"
|
||||
],
|
||||
"time": "2018-04-29T14:44:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php70",
|
||||
"version": "v1.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php70.git",
|
||||
"reference": "77454693d8f10dd23bb24955cffd2d82db1007a6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/77454693d8f10dd23bb24955cffd2d82db1007a6",
|
||||
"reference": "77454693d8f10dd23bb24955cffd2d82db1007a6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"paragonie/random_compat": "~1.0|~2.0",
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.8-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php70\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"time": "2018-04-26T10:06:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "vlucas/valitron",
|
||||
"version": "v1.4.2",
|
||||
|
||||
+7
@@ -6,14 +6,21 @@ $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',
|
||||
'DB' => $vendorDir . '/sergeytsalkov/meekrodb/db.class.php',
|
||||
'DBHelper' => $vendorDir . '/sergeytsalkov/meekrodb/db.class.php',
|
||||
'DBTransaction' => $vendorDir . '/sergeytsalkov/meekrodb/db.class.php',
|
||||
'DivisionByZeroError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php',
|
||||
'Error' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/Error.php',
|
||||
'FB' => $vendorDir . '/firephp/firephp-core/lib/FirePHPCore/fb.php',
|
||||
'FirePHP' => $vendorDir . '/firephp/firephp-core/lib/FirePHPCore/FirePHP.class.php',
|
||||
'MeekroDB' => $vendorDir . '/sergeytsalkov/meekrodb/db.class.php',
|
||||
'MeekroDBEval' => $vendorDir . '/sergeytsalkov/meekrodb/db.class.php',
|
||||
'MeekroDBException' => $vendorDir . '/sergeytsalkov/meekrodb/db.class.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',
|
||||
'WhereClause' => $vendorDir . '/sergeytsalkov/meekrodb/db.class.php',
|
||||
'kakao\\KakaoKey' => $baseDir . '/d_setting/KakaoKey.php',
|
||||
'sammo\\RootDB' => $baseDir . '/d_setting/RootDB.php',
|
||||
|
||||
Vendored
+2
@@ -6,8 +6,10 @@ $vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php',
|
||||
'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
|
||||
'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
|
||||
'023d27dca8066ef29e6739335ea73bad' => $vendorDir . '/symfony/polyfill-php70/bootstrap.php',
|
||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||
'5b154887902198b16314243c6e0e3e19' => $vendorDir . '/pguardiario/phpuri/phpuri.php',
|
||||
'870dc64919afa8b0f700701bb2c6a783' => $baseDir . '/f_config/config.php',
|
||||
|
||||
Vendored
+2
@@ -8,6 +8,8 @@ $baseDir = dirname($vendorDir);
|
||||
return array(
|
||||
'sammo\\' => array($baseDir . '/src/sammo'),
|
||||
'kakao\\' => array($baseDir . '/src/kakao'),
|
||||
'Symfony\\Polyfill\\Php70\\' => array($vendorDir . '/symfony/polyfill-php70'),
|
||||
'Symfony\\Component\\Lock\\' => array($vendorDir . '/symfony/lock'),
|
||||
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
|
||||
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
|
||||
'PhpExtended\\Tail\\' => array($vendorDir . '/php-extended/php-tail/src'),
|
||||
|
||||
Vendored
+22
@@ -7,8 +7,10 @@ namespace Composer\Autoload;
|
||||
class ComposerStaticInit67b09c83b85c3bc0027caefe5bba171a
|
||||
{
|
||||
public static $files = array (
|
||||
'5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php',
|
||||
'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
|
||||
'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
|
||||
'023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php',
|
||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||
'5b154887902198b16314243c6e0e3e19' => __DIR__ . '/..' . '/pguardiario/phpuri/phpuri.php',
|
||||
'870dc64919afa8b0f700701bb2c6a783' => __DIR__ . '/../..' . '/f_config/config.php',
|
||||
@@ -23,6 +25,11 @@ class ComposerStaticInit67b09c83b85c3bc0027caefe5bba171a
|
||||
array (
|
||||
'kakao\\' => 6,
|
||||
),
|
||||
'S' =>
|
||||
array (
|
||||
'Symfony\\Polyfill\\Php70\\' => 23,
|
||||
'Symfony\\Component\\Lock\\' => 23,
|
||||
),
|
||||
'P' =>
|
||||
array (
|
||||
'Psr\\Log\\' => 8,
|
||||
@@ -56,6 +63,14 @@ class ComposerStaticInit67b09c83b85c3bc0027caefe5bba171a
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/src/kakao',
|
||||
),
|
||||
'Symfony\\Polyfill\\Php70\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-php70',
|
||||
),
|
||||
'Symfony\\Component\\Lock\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/lock',
|
||||
),
|
||||
'Psr\\Log\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
|
||||
@@ -116,14 +131,21 @@ class ComposerStaticInit67b09c83b85c3bc0027caefe5bba171a
|
||||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'ArithmeticError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php',
|
||||
'AssertionError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php',
|
||||
'DB' => __DIR__ . '/..' . '/sergeytsalkov/meekrodb/db.class.php',
|
||||
'DBHelper' => __DIR__ . '/..' . '/sergeytsalkov/meekrodb/db.class.php',
|
||||
'DBTransaction' => __DIR__ . '/..' . '/sergeytsalkov/meekrodb/db.class.php',
|
||||
'DivisionByZeroError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php',
|
||||
'Error' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/Error.php',
|
||||
'FB' => __DIR__ . '/..' . '/firephp/firephp-core/lib/FirePHPCore/fb.php',
|
||||
'FirePHP' => __DIR__ . '/..' . '/firephp/firephp-core/lib/FirePHPCore/FirePHP.class.php',
|
||||
'MeekroDB' => __DIR__ . '/..' . '/sergeytsalkov/meekrodb/db.class.php',
|
||||
'MeekroDBEval' => __DIR__ . '/..' . '/sergeytsalkov/meekrodb/db.class.php',
|
||||
'MeekroDBException' => __DIR__ . '/..' . '/sergeytsalkov/meekrodb/db.class.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',
|
||||
'WhereClause' => __DIR__ . '/..' . '/sergeytsalkov/meekrodb/db.class.php',
|
||||
'kakao\\KakaoKey' => __DIR__ . '/../..' . '/d_setting/KakaoKey.php',
|
||||
'sammo\\RootDB' => __DIR__ . '/../..' . '/d_setting/RootDB.php',
|
||||
|
||||
Vendored
+175
@@ -463,6 +463,56 @@
|
||||
"psr-3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v2.0.12",
|
||||
"version_normalized": "2.0.12.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/random_compat.git",
|
||||
"reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb",
|
||||
"reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*|5.*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||
},
|
||||
"time": "2018-04-04T21:24:14+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/random.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
||||
"keywords": [
|
||||
"csprng",
|
||||
"pseudorandom",
|
||||
"random"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pguardiario/phpuri",
|
||||
"version": "1.0",
|
||||
@@ -723,6 +773,131 @@
|
||||
"pdo"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "symfony/lock",
|
||||
"version": "v3.4.9",
|
||||
"version_normalized": "3.4.9.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/lock.git",
|
||||
"reference": "3a707193326d31603824706e34a95af899a59788"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/lock/zipball/3a707193326d31603824706e34a95af899a59788",
|
||||
"reference": "3a707193326d31603824706e34a95af899a59788",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.5.9|>=7.0.8",
|
||||
"psr/log": "~1.0",
|
||||
"symfony/polyfill-php70": "~1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"predis/predis": "~1.0"
|
||||
},
|
||||
"time": "2018-04-29T14:44:26+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.4-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Lock\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
},
|
||||
{
|
||||
"name": "Jérémy Derussé",
|
||||
"email": "jeremy@derusse.com"
|
||||
}
|
||||
],
|
||||
"description": "Symfony Lock Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"cas",
|
||||
"flock",
|
||||
"locking",
|
||||
"mutex",
|
||||
"redlock",
|
||||
"semaphore"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php70",
|
||||
"version": "v1.8.0",
|
||||
"version_normalized": "1.8.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php70.git",
|
||||
"reference": "77454693d8f10dd23bb24955cffd2d82db1007a6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/77454693d8f10dd23bb24955cffd2d82db1007a6",
|
||||
"reference": "77454693d8f10dd23bb24955cffd2d82db1007a6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"paragonie/random_compat": "~1.0|~2.0",
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"time": "2018-04-26T10:06:28+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.8-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php70\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "vlucas/valitron",
|
||||
"version": "v1.4.2",
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Paragon Initiative Enterprises
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) )
|
||||
|
||||
php -dphar.readonly=0 "$basedir/other/build_phar.php" $*
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
||||
"keywords": [
|
||||
"csprng",
|
||||
"random",
|
||||
"pseudorandom"
|
||||
],
|
||||
"license": "MIT",
|
||||
"type": "library",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/paragonie/random_compat/issues",
|
||||
"email": "info@paragonie.com",
|
||||
"source": "https://github.com/paragonie/random_compat"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*|5.*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/random.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm
|
||||
pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p
|
||||
+h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -0,0 +1,11 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
Version: GnuPG v2.0.22 (MingW32)
|
||||
|
||||
iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip
|
||||
QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg
|
||||
1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW
|
||||
NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA
|
||||
NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV
|
||||
JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74=
|
||||
=B6+8
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
/**
|
||||
* Random_* Compatibility Library
|
||||
* for using the new PHP 7 random_* API in PHP 5 projects
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
if (!is_callable('RandomCompat_strlen')) {
|
||||
if (
|
||||
defined('MB_OVERLOAD_STRING') &&
|
||||
ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING
|
||||
) {
|
||||
/**
|
||||
* strlen() implementation that isn't brittle to mbstring.func_overload
|
||||
*
|
||||
* This version uses mb_strlen() in '8bit' mode to treat strings as raw
|
||||
* binary rather than UTF-8, ISO-8859-1, etc
|
||||
*
|
||||
* @param string $binary_string
|
||||
*
|
||||
* @throws TypeError
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function RandomCompat_strlen($binary_string)
|
||||
{
|
||||
if (!is_string($binary_string)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_strlen() expects a string'
|
||||
);
|
||||
}
|
||||
|
||||
return (int) mb_strlen($binary_string, '8bit');
|
||||
}
|
||||
|
||||
} else {
|
||||
/**
|
||||
* strlen() implementation that isn't brittle to mbstring.func_overload
|
||||
*
|
||||
* This version just used the default strlen()
|
||||
*
|
||||
* @param string $binary_string
|
||||
*
|
||||
* @throws TypeError
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function RandomCompat_strlen($binary_string)
|
||||
{
|
||||
if (!is_string($binary_string)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_strlen() expects a string'
|
||||
);
|
||||
}
|
||||
return (int) strlen($binary_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_callable('RandomCompat_substr')) {
|
||||
|
||||
if (
|
||||
defined('MB_OVERLOAD_STRING')
|
||||
&&
|
||||
ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING
|
||||
) {
|
||||
/**
|
||||
* substr() implementation that isn't brittle to mbstring.func_overload
|
||||
*
|
||||
* This version uses mb_substr() in '8bit' mode to treat strings as raw
|
||||
* binary rather than UTF-8, ISO-8859-1, etc
|
||||
*
|
||||
* @param string $binary_string
|
||||
* @param int $start
|
||||
* @param int $length (optional)
|
||||
*
|
||||
* @throws TypeError
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function RandomCompat_substr($binary_string, $start, $length = null)
|
||||
{
|
||||
if (!is_string($binary_string)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_substr(): First argument should be a string'
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_int($start)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_substr(): Second argument should be an integer'
|
||||
);
|
||||
}
|
||||
|
||||
if ($length === null) {
|
||||
/**
|
||||
* mb_substr($str, 0, NULL, '8bit') returns an empty string on
|
||||
* PHP 5.3, so we have to find the length ourselves.
|
||||
*/
|
||||
$length = RandomCompat_strlen($binary_string) - $start;
|
||||
} elseif (!is_int($length)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_substr(): Third argument should be an integer, or omitted'
|
||||
);
|
||||
}
|
||||
|
||||
// Consistency with PHP's behavior
|
||||
if ($start === RandomCompat_strlen($binary_string) && $length === 0) {
|
||||
return '';
|
||||
}
|
||||
if ($start > RandomCompat_strlen($binary_string)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string) mb_substr($binary_string, $start, $length, '8bit');
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
/**
|
||||
* substr() implementation that isn't brittle to mbstring.func_overload
|
||||
*
|
||||
* This version just uses the default substr()
|
||||
*
|
||||
* @param string $binary_string
|
||||
* @param int $start
|
||||
* @param int $length (optional)
|
||||
*
|
||||
* @throws TypeError
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function RandomCompat_substr($binary_string, $start, $length = null)
|
||||
{
|
||||
if (!is_string($binary_string)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_substr(): First argument should be a string'
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_int($start)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_substr(): Second argument should be an integer'
|
||||
);
|
||||
}
|
||||
|
||||
if ($length !== null) {
|
||||
if (!is_int($length)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_substr(): Third argument should be an integer, or omitted'
|
||||
);
|
||||
}
|
||||
|
||||
return (string) substr($binary_string, $start, $length);
|
||||
}
|
||||
|
||||
return (string) substr($binary_string, $start);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* Random_* Compatibility Library
|
||||
* for using the new PHP 7 random_* API in PHP 5 projects
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
if (!is_callable('RandomCompat_intval')) {
|
||||
|
||||
/**
|
||||
* Cast to an integer if we can, safely.
|
||||
*
|
||||
* If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
|
||||
* (non-inclusive), it will sanely cast it to an int. If you it's equal to
|
||||
* ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
|
||||
* lose precision, so the <= and => operators might accidentally let a float
|
||||
* through.
|
||||
*
|
||||
* @param int|float $number The number we want to convert to an int
|
||||
* @param bool $fail_open Set to true to not throw an exception
|
||||
*
|
||||
* @return float|int
|
||||
* @psalm-suppress InvalidReturnType
|
||||
*
|
||||
* @throws TypeError
|
||||
*/
|
||||
function RandomCompat_intval($number, $fail_open = false)
|
||||
{
|
||||
if (is_int($number) || is_float($number)) {
|
||||
$number += 0;
|
||||
} elseif (is_numeric($number)) {
|
||||
$number += 0;
|
||||
}
|
||||
|
||||
if (
|
||||
is_float($number)
|
||||
&&
|
||||
$number > ~PHP_INT_MAX
|
||||
&&
|
||||
$number < PHP_INT_MAX
|
||||
) {
|
||||
$number = (int) $number;
|
||||
}
|
||||
|
||||
if (is_int($number)) {
|
||||
return (int) $number;
|
||||
} elseif (!$fail_open) {
|
||||
throw new TypeError(
|
||||
'Expected an integer.'
|
||||
);
|
||||
}
|
||||
return $number;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Random_* Compatibility Library
|
||||
* for using the new PHP 7 random_* API in PHP 5 projects
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
if (!class_exists('Error', false)) {
|
||||
// We can't really avoid making this extend Exception in PHP 5.
|
||||
class Error extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists('TypeError', false)) {
|
||||
if (is_subclass_of('Error', 'Exception')) {
|
||||
class TypeError extends Error
|
||||
{
|
||||
|
||||
}
|
||||
} else {
|
||||
class TypeError extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
/**
|
||||
* Random_* Compatibility Library
|
||||
* for using the new PHP 7 random_* API in PHP 5 projects
|
||||
*
|
||||
* @version 2.0.10
|
||||
* @released 2017-03-13
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
if (!defined('PHP_VERSION_ID')) {
|
||||
// This constant was introduced in PHP 5.2.7
|
||||
$RandomCompatversion = array_map('intval', explode('.', PHP_VERSION));
|
||||
define(
|
||||
'PHP_VERSION_ID',
|
||||
$RandomCompatversion[0] * 10000
|
||||
+ $RandomCompatversion[1] * 100
|
||||
+ $RandomCompatversion[2]
|
||||
);
|
||||
$RandomCompatversion = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* PHP 7.0.0 and newer have these functions natively.
|
||||
*/
|
||||
if (PHP_VERSION_ID >= 70000) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
|
||||
define('RANDOM_COMPAT_READ_BUFFER', 8);
|
||||
}
|
||||
|
||||
$RandomCompatDIR = dirname(__FILE__);
|
||||
|
||||
require_once $RandomCompatDIR . '/byte_safe_strings.php';
|
||||
require_once $RandomCompatDIR . '/cast_to_int.php';
|
||||
require_once $RandomCompatDIR . '/error_polyfill.php';
|
||||
|
||||
if (!is_callable('random_bytes')) {
|
||||
/**
|
||||
* PHP 5.2.0 - 5.6.x way to implement random_bytes()
|
||||
*
|
||||
* We use conditional statements here to define the function in accordance
|
||||
* to the operating environment. It's a micro-optimization.
|
||||
*
|
||||
* In order of preference:
|
||||
* 1. Use libsodium if available.
|
||||
* 2. fread() /dev/urandom if available (never on Windows)
|
||||
* 3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM)
|
||||
* 4. COM('CAPICOM.Utilities.1')->GetRandom()
|
||||
*
|
||||
* See RATIONALE.md for our reasoning behind this particular order
|
||||
*/
|
||||
if (extension_loaded('libsodium')) {
|
||||
// See random_bytes_libsodium.php
|
||||
if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) {
|
||||
require_once $RandomCompatDIR . '/random_bytes_libsodium.php';
|
||||
} elseif (method_exists('Sodium', 'randombytes_buf')) {
|
||||
require_once $RandomCompatDIR . '/random_bytes_libsodium_legacy.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reading directly from /dev/urandom:
|
||||
*/
|
||||
if (DIRECTORY_SEPARATOR === '/') {
|
||||
// DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast
|
||||
// way to exclude Windows.
|
||||
$RandomCompatUrandom = true;
|
||||
$RandomCompat_basedir = ini_get('open_basedir');
|
||||
|
||||
if (!empty($RandomCompat_basedir)) {
|
||||
$RandomCompat_open_basedir = explode(
|
||||
PATH_SEPARATOR,
|
||||
strtolower($RandomCompat_basedir)
|
||||
);
|
||||
$RandomCompatUrandom = (array() !== array_intersect(
|
||||
array('/dev', '/dev/', '/dev/urandom'),
|
||||
$RandomCompat_open_basedir
|
||||
));
|
||||
$RandomCompat_open_basedir = null;
|
||||
}
|
||||
|
||||
if (
|
||||
!is_callable('random_bytes')
|
||||
&&
|
||||
$RandomCompatUrandom
|
||||
&&
|
||||
@is_readable('/dev/urandom')
|
||||
) {
|
||||
// Error suppression on is_readable() in case of an open_basedir
|
||||
// or safe_mode failure. All we care about is whether or not we
|
||||
// can read it at this point. If the PHP environment is going to
|
||||
// panic over trying to see if the file can be read in the first
|
||||
// place, that is not helpful to us here.
|
||||
|
||||
// See random_bytes_dev_urandom.php
|
||||
require_once $RandomCompatDIR . '/random_bytes_dev_urandom.php';
|
||||
}
|
||||
// Unset variables after use
|
||||
$RandomCompat_basedir = null;
|
||||
} else {
|
||||
$RandomCompatUrandom = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* mcrypt_create_iv()
|
||||
*
|
||||
* We only want to use mcypt_create_iv() if:
|
||||
*
|
||||
* - random_bytes() hasn't already been defined
|
||||
* - the mcrypt extensions is loaded
|
||||
* - One of these two conditions is true:
|
||||
* - We're on Windows (DIRECTORY_SEPARATOR !== '/')
|
||||
* - We're not on Windows and /dev/urandom is readabale
|
||||
* (i.e. we're not in a chroot jail)
|
||||
* - Special case:
|
||||
* - If we're not on Windows, but the PHP version is between
|
||||
* 5.6.10 and 5.6.12, we don't want to use mcrypt. It will
|
||||
* hang indefinitely. This is bad.
|
||||
* - If we're on Windows, we want to use PHP >= 5.3.7 or else
|
||||
* we get insufficient entropy errors.
|
||||
*/
|
||||
if (
|
||||
!is_callable('random_bytes')
|
||||
&&
|
||||
// Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be.
|
||||
(DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307)
|
||||
&&
|
||||
// Prevent this code from hanging indefinitely on non-Windows;
|
||||
// see https://bugs.php.net/bug.php?id=69833
|
||||
(
|
||||
DIRECTORY_SEPARATOR !== '/' ||
|
||||
(PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613)
|
||||
)
|
||||
&&
|
||||
extension_loaded('mcrypt')
|
||||
) {
|
||||
// See random_bytes_mcrypt.php
|
||||
require_once $RandomCompatDIR . '/random_bytes_mcrypt.php';
|
||||
}
|
||||
$RandomCompatUrandom = null;
|
||||
|
||||
/**
|
||||
* This is a Windows-specific fallback, for when the mcrypt extension
|
||||
* isn't loaded.
|
||||
*/
|
||||
if (
|
||||
!is_callable('random_bytes')
|
||||
&&
|
||||
extension_loaded('com_dotnet')
|
||||
&&
|
||||
class_exists('COM')
|
||||
) {
|
||||
$RandomCompat_disabled_classes = preg_split(
|
||||
'#\s*,\s*#',
|
||||
strtolower(ini_get('disable_classes'))
|
||||
);
|
||||
|
||||
if (!in_array('com', $RandomCompat_disabled_classes)) {
|
||||
try {
|
||||
$RandomCompatCOMtest = new COM('CAPICOM.Utilities.1');
|
||||
if (method_exists($RandomCompatCOMtest, 'GetRandom')) {
|
||||
// See random_bytes_com_dotnet.php
|
||||
require_once $RandomCompatDIR . '/random_bytes_com_dotnet.php';
|
||||
}
|
||||
} catch (com_exception $e) {
|
||||
// Don't try to use it.
|
||||
}
|
||||
}
|
||||
$RandomCompat_disabled_classes = null;
|
||||
$RandomCompatCOMtest = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* throw new Exception
|
||||
*/
|
||||
if (!is_callable('random_bytes')) {
|
||||
/**
|
||||
* We don't have any more options, so let's throw an exception right now
|
||||
* and hope the developer won't let it fail silently.
|
||||
*
|
||||
* @param mixed $length
|
||||
* @psalm-suppress MissingReturnType
|
||||
* @throws Exception
|
||||
* @return string
|
||||
*/
|
||||
function random_bytes($length)
|
||||
{
|
||||
unset($length); // Suppress "variable not used" warnings.
|
||||
throw new Exception(
|
||||
'There is no suitable CSPRNG installed on your system'
|
||||
);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_callable('random_int')) {
|
||||
require_once $RandomCompatDIR . '/random_int.php';
|
||||
}
|
||||
|
||||
$RandomCompatDIR = null;
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* Random_* Compatibility Library
|
||||
* for using the new PHP 7 random_* API in PHP 5 projects
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
if (!is_callable('random_bytes')) {
|
||||
/**
|
||||
* Windows with PHP < 5.3.0 will not have the function
|
||||
* openssl_random_pseudo_bytes() available, so let's use
|
||||
* CAPICOM to work around this deficiency.
|
||||
*
|
||||
* @param int $bytes
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function random_bytes($bytes)
|
||||
{
|
||||
try {
|
||||
$bytes = RandomCompat_intval($bytes);
|
||||
} catch (TypeError $ex) {
|
||||
throw new TypeError(
|
||||
'random_bytes(): $bytes must be an integer'
|
||||
);
|
||||
}
|
||||
|
||||
if ($bytes < 1) {
|
||||
throw new Error(
|
||||
'Length must be greater than 0'
|
||||
);
|
||||
}
|
||||
|
||||
$buf = '';
|
||||
if (!class_exists('COM')) {
|
||||
throw new Error(
|
||||
'COM does not exist'
|
||||
);
|
||||
}
|
||||
$util = new COM('CAPICOM.Utilities.1');
|
||||
$execCount = 0;
|
||||
|
||||
/**
|
||||
* Let's not let it loop forever. If we run N times and fail to
|
||||
* get N bytes of random data, then CAPICOM has failed us.
|
||||
*/
|
||||
do {
|
||||
$buf .= base64_decode($util->GetRandom($bytes, 0));
|
||||
if (RandomCompat_strlen($buf) >= $bytes) {
|
||||
/**
|
||||
* Return our random entropy buffer here:
|
||||
*/
|
||||
return RandomCompat_substr($buf, 0, $bytes);
|
||||
}
|
||||
++$execCount;
|
||||
} while ($execCount < $bytes);
|
||||
|
||||
/**
|
||||
* If we reach here, PHP has failed us.
|
||||
*/
|
||||
throw new Exception(
|
||||
'Could not gather sufficient random data'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
/**
|
||||
* Random_* Compatibility Library
|
||||
* for using the new PHP 7 random_* API in PHP 5 projects
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
|
||||
define('RANDOM_COMPAT_READ_BUFFER', 8);
|
||||
}
|
||||
|
||||
if (!is_callable('random_bytes')) {
|
||||
/**
|
||||
* Unless open_basedir is enabled, use /dev/urandom for
|
||||
* random numbers in accordance with best practices
|
||||
*
|
||||
* Why we use /dev/urandom and not /dev/random
|
||||
* @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers
|
||||
*
|
||||
* @param int $bytes
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function random_bytes($bytes)
|
||||
{
|
||||
static $fp = null;
|
||||
/**
|
||||
* This block should only be run once
|
||||
*/
|
||||
if (empty($fp)) {
|
||||
/**
|
||||
* We use /dev/urandom if it is a char device.
|
||||
* We never fall back to /dev/random
|
||||
*/
|
||||
$fp = fopen('/dev/urandom', 'rb');
|
||||
if (!empty($fp)) {
|
||||
$st = fstat($fp);
|
||||
if (($st['mode'] & 0170000) !== 020000) {
|
||||
fclose($fp);
|
||||
$fp = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($fp)) {
|
||||
/**
|
||||
* stream_set_read_buffer() does not exist in HHVM
|
||||
*
|
||||
* If we don't set the stream's read buffer to 0, PHP will
|
||||
* internally buffer 8192 bytes, which can waste entropy
|
||||
*
|
||||
* stream_set_read_buffer returns 0 on success
|
||||
*/
|
||||
if (is_callable('stream_set_read_buffer')) {
|
||||
stream_set_read_buffer($fp, RANDOM_COMPAT_READ_BUFFER);
|
||||
}
|
||||
if (is_callable('stream_set_chunk_size')) {
|
||||
stream_set_chunk_size($fp, RANDOM_COMPAT_READ_BUFFER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$bytes = RandomCompat_intval($bytes);
|
||||
} catch (TypeError $ex) {
|
||||
throw new TypeError(
|
||||
'random_bytes(): $bytes must be an integer'
|
||||
);
|
||||
}
|
||||
|
||||
if ($bytes < 1) {
|
||||
throw new Error(
|
||||
'Length must be greater than 0'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This if() block only runs if we managed to open a file handle
|
||||
*
|
||||
* It does not belong in an else {} block, because the above
|
||||
* if (empty($fp)) line is logic that should only be run once per
|
||||
* page load.
|
||||
*/
|
||||
if (!empty($fp)) {
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
$remaining = $bytes;
|
||||
|
||||
/**
|
||||
* @var string|bool
|
||||
*/
|
||||
$buf = '';
|
||||
|
||||
/**
|
||||
* We use fread() in a loop to protect against partial reads
|
||||
*/
|
||||
do {
|
||||
/**
|
||||
* @var string|bool
|
||||
*/
|
||||
$read = fread($fp, $remaining);
|
||||
if (!is_string($read)) {
|
||||
if ($read === false) {
|
||||
/**
|
||||
* We cannot safely read from the file. Exit the
|
||||
* do-while loop and trigger the exception condition
|
||||
*
|
||||
* @var string|bool
|
||||
*/
|
||||
$buf = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Decrease the number of bytes returned from remaining
|
||||
*/
|
||||
$remaining -= RandomCompat_strlen($read);
|
||||
/**
|
||||
* @var string|bool
|
||||
*/
|
||||
$buf = $buf . $read;
|
||||
} while ($remaining > 0);
|
||||
|
||||
/**
|
||||
* Is our result valid?
|
||||
*/
|
||||
if (is_string($buf)) {
|
||||
if (RandomCompat_strlen($buf) === $bytes) {
|
||||
/**
|
||||
* Return our random entropy buffer here:
|
||||
*/
|
||||
return $buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we reach here, PHP has failed us.
|
||||
*/
|
||||
throw new Exception(
|
||||
'Error reading from source device'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* Random_* Compatibility Library
|
||||
* for using the new PHP 7 random_* API in PHP 5 projects
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
if (!is_callable('random_bytes')) {
|
||||
/**
|
||||
* If the libsodium PHP extension is loaded, we'll use it above any other
|
||||
* solution.
|
||||
*
|
||||
* libsodium-php project:
|
||||
* @ref https://github.com/jedisct1/libsodium-php
|
||||
*
|
||||
* @param int $bytes
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function random_bytes($bytes)
|
||||
{
|
||||
try {
|
||||
$bytes = RandomCompat_intval($bytes);
|
||||
} catch (TypeError $ex) {
|
||||
throw new TypeError(
|
||||
'random_bytes(): $bytes must be an integer'
|
||||
);
|
||||
}
|
||||
|
||||
if ($bytes < 1) {
|
||||
throw new Error(
|
||||
'Length must be greater than 0'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be
|
||||
* generated in one invocation.
|
||||
*/
|
||||
if ($bytes > 2147483647) {
|
||||
$buf = '';
|
||||
for ($i = 0; $i < $bytes; $i += 1073741824) {
|
||||
$n = ($bytes - $i) > 1073741824
|
||||
? 1073741824
|
||||
: $bytes - $i;
|
||||
$buf .= \Sodium\randombytes_buf($n);
|
||||
}
|
||||
} else {
|
||||
$buf = \Sodium\randombytes_buf($bytes);
|
||||
}
|
||||
|
||||
if ($buf !== false) {
|
||||
if (RandomCompat_strlen($buf) === $bytes) {
|
||||
return $buf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we reach here, PHP has failed us.
|
||||
*/
|
||||
throw new Exception(
|
||||
'Could not gather sufficient random data'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* Random_* Compatibility Library
|
||||
* for using the new PHP 7 random_* API in PHP 5 projects
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
if (!is_callable('random_bytes')) {
|
||||
/**
|
||||
* If the libsodium PHP extension is loaded, we'll use it above any other
|
||||
* solution.
|
||||
*
|
||||
* libsodium-php project:
|
||||
* @ref https://github.com/jedisct1/libsodium-php
|
||||
*
|
||||
* @param int $bytes
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function random_bytes($bytes)
|
||||
{
|
||||
try {
|
||||
$bytes = RandomCompat_intval($bytes);
|
||||
} catch (TypeError $ex) {
|
||||
throw new TypeError(
|
||||
'random_bytes(): $bytes must be an integer'
|
||||
);
|
||||
}
|
||||
|
||||
if ($bytes < 1) {
|
||||
throw new Error(
|
||||
'Length must be greater than 0'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
$buf = '';
|
||||
|
||||
/**
|
||||
* \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be
|
||||
* generated in one invocation.
|
||||
*/
|
||||
if ($bytes > 2147483647) {
|
||||
for ($i = 0; $i < $bytes; $i += 1073741824) {
|
||||
$n = ($bytes - $i) > 1073741824
|
||||
? 1073741824
|
||||
: $bytes - $i;
|
||||
$buf .= Sodium::randombytes_buf((int) $n);
|
||||
}
|
||||
} else {
|
||||
$buf .= Sodium::randombytes_buf((int) $bytes);
|
||||
}
|
||||
|
||||
if (is_string($buf)) {
|
||||
if (RandomCompat_strlen($buf) === $bytes) {
|
||||
return $buf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we reach here, PHP has failed us.
|
||||
*/
|
||||
throw new Exception(
|
||||
'Could not gather sufficient random data'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* Random_* Compatibility Library
|
||||
* for using the new PHP 7 random_* API in PHP 5 projects
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
if (!is_callable('random_bytes')) {
|
||||
/**
|
||||
* Powered by ext/mcrypt (and thankfully NOT libmcrypt)
|
||||
*
|
||||
* @ref https://bugs.php.net/bug.php?id=55169
|
||||
* @ref https://github.com/php/php-src/blob/c568ffe5171d942161fc8dda066bce844bdef676/ext/mcrypt/mcrypt.c#L1321-L1386
|
||||
*
|
||||
* @param int $bytes
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function random_bytes($bytes)
|
||||
{
|
||||
try {
|
||||
$bytes = RandomCompat_intval($bytes);
|
||||
} catch (TypeError $ex) {
|
||||
throw new TypeError(
|
||||
'random_bytes(): $bytes must be an integer'
|
||||
);
|
||||
}
|
||||
|
||||
if ($bytes < 1) {
|
||||
throw new Error(
|
||||
'Length must be greater than 0'
|
||||
);
|
||||
}
|
||||
|
||||
$buf = @mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
|
||||
if (
|
||||
$buf !== false
|
||||
&&
|
||||
RandomCompat_strlen($buf) === $bytes
|
||||
) {
|
||||
/**
|
||||
* Return our random entropy buffer here:
|
||||
*/
|
||||
return $buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we reach here, PHP has failed us.
|
||||
*/
|
||||
throw new Exception(
|
||||
'Could not gather sufficient random data'
|
||||
);
|
||||
}
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
if (!is_callable('random_int')) {
|
||||
/**
|
||||
* Random_* Compatibility Library
|
||||
* for using the new PHP 7 random_* API in PHP 5 projects
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetch a random integer between $min and $max inclusive
|
||||
*
|
||||
* @param int $min
|
||||
* @param int $max
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function random_int($min, $max)
|
||||
{
|
||||
/**
|
||||
* Type and input logic checks
|
||||
*
|
||||
* If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
|
||||
* (non-inclusive), it will sanely cast it to an int. If you it's equal to
|
||||
* ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
|
||||
* lose precision, so the <= and => operators might accidentally let a float
|
||||
* through.
|
||||
*/
|
||||
|
||||
try {
|
||||
$min = RandomCompat_intval($min);
|
||||
} catch (TypeError $ex) {
|
||||
throw new TypeError(
|
||||
'random_int(): $min must be an integer'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$max = RandomCompat_intval($max);
|
||||
} catch (TypeError $ex) {
|
||||
throw new TypeError(
|
||||
'random_int(): $max must be an integer'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Now that we've verified our weak typing system has given us an integer,
|
||||
* let's validate the logic then we can move forward with generating random
|
||||
* integers along a given range.
|
||||
*/
|
||||
if ($min > $max) {
|
||||
throw new Error(
|
||||
'Minimum value must be less than or equal to the maximum value'
|
||||
);
|
||||
}
|
||||
|
||||
if ($max === $min) {
|
||||
return (int) $min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize variables to 0
|
||||
*
|
||||
* We want to store:
|
||||
* $bytes => the number of random bytes we need
|
||||
* $mask => an integer bitmask (for use with the &) operator
|
||||
* so we can minimize the number of discards
|
||||
*/
|
||||
$attempts = $bits = $bytes = $mask = $valueShift = 0;
|
||||
|
||||
/**
|
||||
* At this point, $range is a positive number greater than 0. It might
|
||||
* overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
|
||||
* a float and we will lose some precision.
|
||||
*/
|
||||
$range = $max - $min;
|
||||
|
||||
/**
|
||||
* Test for integer overflow:
|
||||
*/
|
||||
if (!is_int($range)) {
|
||||
|
||||
/**
|
||||
* Still safely calculate wider ranges.
|
||||
* Provided by @CodesInChaos, @oittaa
|
||||
*
|
||||
* @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
|
||||
*
|
||||
* We use ~0 as a mask in this case because it generates all 1s
|
||||
*
|
||||
* @ref https://eval.in/400356 (32-bit)
|
||||
* @ref http://3v4l.org/XX9r5 (64-bit)
|
||||
*/
|
||||
$bytes = PHP_INT_SIZE;
|
||||
$mask = ~0;
|
||||
|
||||
} else {
|
||||
|
||||
/**
|
||||
* $bits is effectively ceil(log($range, 2)) without dealing with
|
||||
* type juggling
|
||||
*/
|
||||
while ($range > 0) {
|
||||
if ($bits % 8 === 0) {
|
||||
++$bytes;
|
||||
}
|
||||
++$bits;
|
||||
$range >>= 1;
|
||||
$mask = $mask << 1 | 1;
|
||||
}
|
||||
$valueShift = $min;
|
||||
}
|
||||
|
||||
$val = 0;
|
||||
/**
|
||||
* Now that we have our parameters set up, let's begin generating
|
||||
* random integers until one falls between $min and $max
|
||||
*/
|
||||
do {
|
||||
/**
|
||||
* The rejection probability is at most 0.5, so this corresponds
|
||||
* to a failure probability of 2^-128 for a working RNG
|
||||
*/
|
||||
if ($attempts > 128) {
|
||||
throw new Exception(
|
||||
'random_int: RNG is broken - too many rejections'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Let's grab the necessary number of random bytes
|
||||
*/
|
||||
$randomByteString = random_bytes($bytes);
|
||||
|
||||
/**
|
||||
* Let's turn $randomByteString into an integer
|
||||
*
|
||||
* This uses bitwise operators (<< and |) to build an integer
|
||||
* out of the values extracted from ord()
|
||||
*
|
||||
* Example: [9F] | [6D] | [32] | [0C] =>
|
||||
* 159 + 27904 + 3276800 + 201326592 =>
|
||||
* 204631455
|
||||
*/
|
||||
$val &= 0;
|
||||
for ($i = 0; $i < $bytes; ++$i) {
|
||||
$val |= ord($randomByteString[$i]) << ($i * 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply mask
|
||||
*/
|
||||
$val &= $mask;
|
||||
$val += $valueShift;
|
||||
|
||||
++$attempts;
|
||||
/**
|
||||
* If $val overflows to a floating point number,
|
||||
* ... or is larger than $max,
|
||||
* ... or smaller than $min,
|
||||
* then try again.
|
||||
*/
|
||||
} while (!is_int($val) || $val > $max || $val < $min);
|
||||
|
||||
return (int) $val;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
$dist = dirname(__DIR__).'/dist';
|
||||
if (!is_dir($dist)) {
|
||||
mkdir($dist, 0755);
|
||||
}
|
||||
if (file_exists($dist.'/random_compat.phar')) {
|
||||
unlink($dist.'/random_compat.phar');
|
||||
}
|
||||
$phar = new Phar(
|
||||
$dist.'/random_compat.phar',
|
||||
FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::KEY_AS_FILENAME,
|
||||
'random_compat.phar'
|
||||
);
|
||||
rename(
|
||||
dirname(__DIR__).'/lib/random.php',
|
||||
dirname(__DIR__).'/lib/index.php'
|
||||
);
|
||||
$phar->buildFromDirectory(dirname(__DIR__).'/lib');
|
||||
rename(
|
||||
dirname(__DIR__).'/lib/index.php',
|
||||
dirname(__DIR__).'/lib/random.php'
|
||||
);
|
||||
|
||||
/**
|
||||
* If we pass an (optional) path to a private key as a second argument, we will
|
||||
* sign the Phar with OpenSSL.
|
||||
*
|
||||
* If you leave this out, it will produce an unsigned .phar!
|
||||
*/
|
||||
if ($argc > 1) {
|
||||
if (!@is_readable($argv[1])) {
|
||||
echo 'Could not read the private key file:', $argv[1], "\n";
|
||||
exit(255);
|
||||
}
|
||||
$pkeyFile = file_get_contents($argv[1]);
|
||||
|
||||
$private = openssl_get_privatekey($pkeyFile);
|
||||
if ($private !== false) {
|
||||
$pkey = '';
|
||||
openssl_pkey_export($private, $pkey);
|
||||
$phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey);
|
||||
|
||||
/**
|
||||
* Save the corresponding public key to the file
|
||||
*/
|
||||
if (!@is_readable($dist.'/random_compat.phar.pubkey')) {
|
||||
$details = openssl_pkey_get_details($private);
|
||||
file_put_contents(
|
||||
$dist.'/random_compat.phar.pubkey',
|
||||
$details['key']
|
||||
);
|
||||
}
|
||||
} else {
|
||||
echo 'An error occurred reading the private key from OpenSSL.', "\n";
|
||||
exit(255);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
require_once 'lib/byte_safe_strings.php';
|
||||
require_once 'lib/cast_to_int.php';
|
||||
require_once 'lib/error_polyfill.php';
|
||||
require_once 'other/ide_stubs/libsodium.php';
|
||||
require_once 'lib/random.php';
|
||||
|
||||
$int = random_int(0, 65536);
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
autoloader="psalm-autoload.php"
|
||||
stopOnFirstError="false"
|
||||
useDocblockTypes="true"
|
||||
>
|
||||
<projectFiles>
|
||||
<directory name="lib" />
|
||||
</projectFiles>
|
||||
<issueHandlers>
|
||||
<RedundantConditionGivenDocblockType errorLevel="info" />
|
||||
<UnresolvableInclude errorLevel="info" />
|
||||
<DuplicateClass errorLevel="info" />
|
||||
<InvalidOperand errorLevel="info" />
|
||||
<UndefinedConstant errorLevel="info" />
|
||||
<MissingReturnType errorLevel="info" />
|
||||
</issueHandlers>
|
||||
</psalm>
|
||||
@@ -0,0 +1,3 @@
|
||||
composer.lock
|
||||
phpunit.xml
|
||||
vendor/
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
* added the component
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Exception;
|
||||
|
||||
/**
|
||||
* Base ExceptionInterface for the Lock Component.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
interface ExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Exception;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Exception;
|
||||
|
||||
/**
|
||||
* LockAcquiringException is thrown when an issue happens during the acquisition of a lock.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class LockAcquiringException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Exception;
|
||||
|
||||
/**
|
||||
* LockConflictedException is thrown when a lock is acquired by someone else.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class LockConflictedException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Exception;
|
||||
|
||||
/**
|
||||
* LockExpiredException is thrown when a lock may conflict due to a TTL expiration.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class LockExpiredException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Exception;
|
||||
|
||||
/**
|
||||
* LockReleasingException is thrown when an issue happens during the release of a lock.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class LockReleasingException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Exception;
|
||||
|
||||
/**
|
||||
* LockStorageException is thrown when an issue happens during the manipulation of a lock in a store.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class LockStorageException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Exception;
|
||||
|
||||
/**
|
||||
* NotSupportedException is thrown when an unsupported method is called.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class NotSupportedException extends \LogicException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock;
|
||||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* Factory provides method to create locks.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class Factory implements LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
private $store;
|
||||
|
||||
public function __construct(StoreInterface $store)
|
||||
{
|
||||
$this->store = $store;
|
||||
|
||||
$this->logger = new NullLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a lock for the given resource.
|
||||
*
|
||||
* @param string $resource The resource to lock
|
||||
* @param float $ttl Maximum expected lock duration in seconds
|
||||
* @param bool $autoRelease Whether to automatically release the lock or not when the lock instance is destroyed
|
||||
*
|
||||
* @return Lock
|
||||
*/
|
||||
public function createLock($resource, $ttl = 300.0, $autoRelease = true)
|
||||
{
|
||||
$lock = new Lock(new Key($resource), $this->store, $ttl, $autoRelease);
|
||||
$lock->setLogger($this->logger);
|
||||
|
||||
return $lock;
|
||||
}
|
||||
}
|
||||
Vendored
+109
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock;
|
||||
|
||||
/**
|
||||
* Key is a container for the state of the locks in stores.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
final class Key
|
||||
{
|
||||
private $resource;
|
||||
private $expiringTime;
|
||||
private $state = array();
|
||||
|
||||
/**
|
||||
* @param string $resource
|
||||
*/
|
||||
public function __construct($resource)
|
||||
{
|
||||
$this->resource = (string) $resource;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $stateKey
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasState($stateKey)
|
||||
{
|
||||
return isset($this->state[$stateKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $stateKey
|
||||
* @param mixed $state
|
||||
*/
|
||||
public function setState($stateKey, $state)
|
||||
{
|
||||
$this->state[$stateKey] = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $stateKey
|
||||
*/
|
||||
public function removeState($stateKey)
|
||||
{
|
||||
unset($this->state[$stateKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $stateKey
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getState($stateKey)
|
||||
{
|
||||
return $this->state[$stateKey];
|
||||
}
|
||||
|
||||
public function resetLifetime()
|
||||
{
|
||||
$this->expiringTime = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $ttl the expiration delay of locks in seconds
|
||||
*/
|
||||
public function reduceLifetime($ttl)
|
||||
{
|
||||
$newTime = microtime(true) + $ttl;
|
||||
|
||||
if (null === $this->expiringTime || $this->expiringTime > $newTime) {
|
||||
$this->expiringTime = $newTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remaining lifetime.
|
||||
*
|
||||
* @return float|null Remaining lifetime in seconds. Null when the key won't expire.
|
||||
*/
|
||||
public function getRemainingLifetime()
|
||||
{
|
||||
return null === $this->expiringTime ? null : $this->expiringTime - microtime(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isExpired()
|
||||
{
|
||||
return null !== $this->expiringTime && $this->expiringTime <= microtime(true);
|
||||
}
|
||||
}
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2016-2018 Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
Vendored
+173
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock;
|
||||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Lock\Exception\LockAcquiringException;
|
||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||
use Symfony\Component\Lock\Exception\LockExpiredException;
|
||||
use Symfony\Component\Lock\Exception\LockReleasingException;
|
||||
|
||||
/**
|
||||
* Lock is the default implementation of the LockInterface.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
final class Lock implements LockInterface, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
private $store;
|
||||
private $key;
|
||||
private $ttl;
|
||||
private $autoRelease;
|
||||
private $dirty = false;
|
||||
|
||||
/**
|
||||
* @param Key $key Resource to lock
|
||||
* @param StoreInterface $store Store used to handle lock persistence
|
||||
* @param float|null $ttl Maximum expected lock duration in seconds
|
||||
* @param bool $autoRelease Whether to automatically release the lock or not when the lock instance is destroyed
|
||||
*/
|
||||
public function __construct(Key $key, StoreInterface $store, $ttl = null, $autoRelease = true)
|
||||
{
|
||||
$this->store = $store;
|
||||
$this->key = $key;
|
||||
$this->ttl = $ttl;
|
||||
$this->autoRelease = (bool) $autoRelease;
|
||||
|
||||
$this->logger = new NullLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically releases the underlying lock when the object is destructed.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (!$this->autoRelease || !$this->dirty || !$this->isAcquired()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->release();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function acquire($blocking = false)
|
||||
{
|
||||
try {
|
||||
if (!$blocking) {
|
||||
$this->store->save($this->key);
|
||||
} else {
|
||||
$this->store->waitAndSave($this->key);
|
||||
}
|
||||
|
||||
$this->dirty = true;
|
||||
$this->logger->info('Successfully acquired the "{resource}" lock.', array('resource' => $this->key));
|
||||
|
||||
if ($this->ttl) {
|
||||
$this->refresh();
|
||||
}
|
||||
|
||||
if ($this->key->isExpired()) {
|
||||
throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $this->key));
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (LockConflictedException $e) {
|
||||
$this->dirty = false;
|
||||
$this->logger->notice('Failed to acquire the "{resource}" lock. Someone else already acquired the lock.', array('resource' => $this->key));
|
||||
|
||||
if ($blocking) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->notice('Failed to acquire the "{resource}" lock.', array('resource' => $this->key, 'exception' => $e));
|
||||
throw new LockAcquiringException(sprintf('Failed to acquire the "%s" lock.', $this->key), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function refresh()
|
||||
{
|
||||
if (!$this->ttl) {
|
||||
throw new InvalidArgumentException('You have to define an expiration duration.');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->key->resetLifetime();
|
||||
$this->store->putOffExpiration($this->key, $this->ttl);
|
||||
$this->dirty = true;
|
||||
|
||||
if ($this->key->isExpired()) {
|
||||
throw new LockExpiredException(sprintf('Failed to put off the expiration of the "%s" lock within the specified time.', $this->key));
|
||||
}
|
||||
|
||||
$this->logger->info('Expiration defined for "{resource}" lock for "{ttl}" seconds.', array('resource' => $this->key, 'ttl' => $this->ttl));
|
||||
} catch (LockConflictedException $e) {
|
||||
$this->dirty = false;
|
||||
$this->logger->notice('Failed to define an expiration for the "{resource}" lock, someone else acquired the lock.', array('resource' => $this->key));
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->notice('Failed to define an expiration for the "{resource}" lock.', array('resource' => $this->key, 'exception' => $e));
|
||||
throw new LockAcquiringException(sprintf('Failed to define an expiration for the "%s" lock.', $this->key), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isAcquired()
|
||||
{
|
||||
return $this->dirty = $this->store->exists($this->key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function release()
|
||||
{
|
||||
$this->store->delete($this->key);
|
||||
$this->dirty = false;
|
||||
|
||||
if ($this->store->exists($this->key)) {
|
||||
$this->logger->notice('Failed to release the "{resource}" lock.', array('resource' => $this->key));
|
||||
throw new LockReleasingException(sprintf('Failed to release the "%s" lock.', $this->key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isExpired()
|
||||
{
|
||||
return $this->key->isExpired();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remaining lifetime.
|
||||
*
|
||||
* @return float|null Remaining lifetime in seconds. Null when the lock won't expire.
|
||||
*/
|
||||
public function getRemainingLifetime()
|
||||
{
|
||||
return $this->key->getRemainingLifetime();
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock;
|
||||
|
||||
use Symfony\Component\Lock\Exception\LockAcquiringException;
|
||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||
use Symfony\Component\Lock\Exception\LockReleasingException;
|
||||
|
||||
/**
|
||||
* LockInterface defines an interface to manipulate the status of a lock.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
interface LockInterface
|
||||
{
|
||||
/**
|
||||
* Acquires the lock. If the lock is acquired by someone else, the parameter `blocking` determines whether or not
|
||||
* the the call should block until the release of the lock.
|
||||
*
|
||||
* @param bool $blocking Whether or not the Lock should wait for the release of someone else
|
||||
*
|
||||
* @return bool whether or not the lock had been acquired
|
||||
*
|
||||
* @throws LockConflictedException If the lock is acquired by someone else in blocking mode
|
||||
* @throws LockAcquiringException If the lock can not be acquired
|
||||
*/
|
||||
public function acquire($blocking = false);
|
||||
|
||||
/**
|
||||
* Increase the duration of an acquired lock.
|
||||
*
|
||||
* @throws LockConflictedException If the lock is acquired by someone else
|
||||
* @throws LockAcquiringException If the lock can not be refreshed
|
||||
*/
|
||||
public function refresh();
|
||||
|
||||
/**
|
||||
* Returns whether or not the lock is acquired.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAcquired();
|
||||
|
||||
/**
|
||||
* Release the lock.
|
||||
*
|
||||
* @throws LockReleasingException If the lock can not be released
|
||||
*/
|
||||
public function release();
|
||||
}
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
Lock Component
|
||||
==============
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Documentation](https://symfony.com/doc/master/components/lock.html)
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
in the [main Symfony repository](https://github.com/symfony/symfony)
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Store;
|
||||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||
use Symfony\Component\Lock\Exception\LockExpiredException;
|
||||
use Symfony\Component\Lock\Exception\NotSupportedException;
|
||||
use Symfony\Component\Lock\Key;
|
||||
use Symfony\Component\Lock\Strategy\StrategyInterface;
|
||||
use Symfony\Component\Lock\StoreInterface;
|
||||
|
||||
/**
|
||||
* CombinedStore is a StoreInterface implementation able to manage and synchronize several StoreInterfaces.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class CombinedStore implements StoreInterface, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/** @var StoreInterface[] */
|
||||
private $stores;
|
||||
/** @var StrategyInterface */
|
||||
private $strategy;
|
||||
|
||||
/**
|
||||
* @param StoreInterface[] $stores The list of synchronized stores
|
||||
* @param StrategyInterface $strategy
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $stores, StrategyInterface $strategy)
|
||||
{
|
||||
foreach ($stores as $store) {
|
||||
if (!$store instanceof StoreInterface) {
|
||||
throw new InvalidArgumentException(sprintf('The store must implement "%s". Got "%s".', StoreInterface::class, get_class($store)));
|
||||
}
|
||||
}
|
||||
|
||||
$this->stores = $stores;
|
||||
$this->strategy = $strategy;
|
||||
$this->logger = new NullLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(Key $key)
|
||||
{
|
||||
$successCount = 0;
|
||||
$failureCount = 0;
|
||||
$storesCount = count($this->stores);
|
||||
|
||||
foreach ($this->stores as $store) {
|
||||
try {
|
||||
$store->save($key);
|
||||
++$successCount;
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->warning('One store failed to save the "{resource}" lock.', array('resource' => $key, 'store' => $store, 'exception' => $e));
|
||||
++$failureCount;
|
||||
}
|
||||
|
||||
if (!$this->strategy->canBeMet($failureCount, $storesCount)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->strategy->isMet($successCount, $storesCount)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logger->warning('Failed to store the "{resource}" lock. Quorum has not been met.', array('resource' => $key, 'success' => $successCount, 'failure' => $failureCount));
|
||||
|
||||
// clean up potential locks
|
||||
$this->delete($key);
|
||||
|
||||
throw new LockConflictedException();
|
||||
}
|
||||
|
||||
public function waitAndSave(Key $key)
|
||||
{
|
||||
throw new NotSupportedException(sprintf('The store "%s" does not supports blocking locks.', get_class($this)));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function putOffExpiration(Key $key, $ttl)
|
||||
{
|
||||
$successCount = 0;
|
||||
$failureCount = 0;
|
||||
$storesCount = count($this->stores);
|
||||
$expireAt = microtime(true) + $ttl;
|
||||
|
||||
foreach ($this->stores as $store) {
|
||||
try {
|
||||
if (0.0 >= $adjustedTtl = $expireAt - microtime(true)) {
|
||||
$this->logger->warning('Stores took to long to put off the expiration of the "{resource}" lock.', array('resource' => $key, 'store' => $store, 'ttl' => $ttl));
|
||||
$key->reduceLifetime(0);
|
||||
break;
|
||||
}
|
||||
|
||||
$store->putOffExpiration($key, $adjustedTtl);
|
||||
++$successCount;
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->warning('One store failed to put off the expiration of the "{resource}" lock.', array('resource' => $key, 'store' => $store, 'exception' => $e));
|
||||
++$failureCount;
|
||||
}
|
||||
|
||||
if (!$this->strategy->canBeMet($failureCount, $storesCount)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($key->isExpired()) {
|
||||
throw new LockExpiredException(sprintf('Failed to put off the expiration of the "%s" lock within the specified time.', $key));
|
||||
}
|
||||
|
||||
if ($this->strategy->isMet($successCount, $storesCount)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logger->warning('Failed to define the expiration for the "{resource}" lock. Quorum has not been met.', array('resource' => $key, 'success' => $successCount, 'failure' => $failureCount));
|
||||
|
||||
// clean up potential locks
|
||||
$this->delete($key);
|
||||
|
||||
throw new LockConflictedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(Key $key)
|
||||
{
|
||||
foreach ($this->stores as $store) {
|
||||
try {
|
||||
$store->delete($key);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->notice('One store failed to delete the "{resource}" lock.', array('resource' => $key, 'store' => $store, 'exception' => $e));
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->notice('One store failed to delete the "{resource}" lock.', array('resource' => $key, 'store' => $store, 'exception' => $e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exists(Key $key)
|
||||
{
|
||||
$successCount = 0;
|
||||
$failureCount = 0;
|
||||
$storesCount = count($this->stores);
|
||||
|
||||
foreach ($this->stores as $store) {
|
||||
if ($store->exists($key)) {
|
||||
++$successCount;
|
||||
} else {
|
||||
++$failureCount;
|
||||
}
|
||||
|
||||
if ($this->strategy->isMet($successCount, $storesCount)) {
|
||||
return true;
|
||||
}
|
||||
if (!$this->strategy->canBeMet($failureCount, $storesCount)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Store;
|
||||
|
||||
use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||
use Symfony\Component\Lock\Exception\LockStorageException;
|
||||
use Symfony\Component\Lock\Key;
|
||||
use Symfony\Component\Lock\StoreInterface;
|
||||
|
||||
/**
|
||||
* FlockStore is a StoreInterface implementation using the FileSystem flock.
|
||||
*
|
||||
* Original implementation in \Symfony\Component\Filesystem\LockHandler.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||
* @author Romain Neutron <imprec@gmail.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class FlockStore implements StoreInterface
|
||||
{
|
||||
private $lockPath;
|
||||
|
||||
/**
|
||||
* @param string|null $lockPath the directory to store the lock, defaults to the system's temporary directory
|
||||
*
|
||||
* @throws LockStorageException If the lock directory doesn’t exist or is not writable
|
||||
*/
|
||||
public function __construct($lockPath = null)
|
||||
{
|
||||
if (null === $lockPath) {
|
||||
$lockPath = sys_get_temp_dir();
|
||||
}
|
||||
if (!is_dir($lockPath) || !is_writable($lockPath)) {
|
||||
throw new InvalidArgumentException(sprintf('The directory "%s" is not writable.', $lockPath));
|
||||
}
|
||||
|
||||
$this->lockPath = $lockPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(Key $key)
|
||||
{
|
||||
$this->lock($key, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function waitAndSave(Key $key)
|
||||
{
|
||||
$this->lock($key, true);
|
||||
}
|
||||
|
||||
private function lock(Key $key, $blocking)
|
||||
{
|
||||
// The lock is maybe already acquired.
|
||||
if ($key->hasState(__CLASS__)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fileName = sprintf('%s/sf.%s.%s.lock',
|
||||
$this->lockPath,
|
||||
preg_replace('/[^a-z0-9\._-]+/i', '-', $key),
|
||||
strtr(substr(base64_encode(hash('sha256', $key, true)), 0, 7), '/', '_')
|
||||
);
|
||||
|
||||
// Silence error reporting
|
||||
set_error_handler(function () {
|
||||
});
|
||||
if (!$handle = fopen($fileName, 'r')) {
|
||||
if ($handle = fopen($fileName, 'x')) {
|
||||
chmod($fileName, 0444);
|
||||
} elseif (!$handle = fopen($fileName, 'r')) {
|
||||
usleep(100); // Give some time for chmod() to complete
|
||||
$handle = fopen($fileName, 'r');
|
||||
}
|
||||
}
|
||||
restore_error_handler();
|
||||
|
||||
if (!$handle) {
|
||||
$error = error_get_last();
|
||||
throw new LockStorageException($error['message'], 0, null);
|
||||
}
|
||||
|
||||
// On Windows, even if PHP doc says the contrary, LOCK_NB works, see
|
||||
// https://bugs.php.net/54129
|
||||
if (!flock($handle, LOCK_EX | ($blocking ? 0 : LOCK_NB))) {
|
||||
fclose($handle);
|
||||
throw new LockConflictedException();
|
||||
}
|
||||
|
||||
$key->setState(__CLASS__, $handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function putOffExpiration(Key $key, $ttl)
|
||||
{
|
||||
// do nothing, the flock locks forever.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(Key $key)
|
||||
{
|
||||
// The lock is maybe not acquired.
|
||||
if (!$key->hasState(__CLASS__)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$handle = $key->getState(__CLASS__);
|
||||
|
||||
flock($handle, LOCK_UN | LOCK_NB);
|
||||
fclose($handle);
|
||||
|
||||
$key->removeState(__CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exists(Key $key)
|
||||
{
|
||||
return $key->hasState(__CLASS__);
|
||||
}
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Store;
|
||||
|
||||
use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||
use Symfony\Component\Lock\Exception\LockExpiredException;
|
||||
use Symfony\Component\Lock\Key;
|
||||
use Symfony\Component\Lock\StoreInterface;
|
||||
|
||||
/**
|
||||
* MemcachedStore is a StoreInterface implementation using Memcached as store engine.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class MemcachedStore implements StoreInterface
|
||||
{
|
||||
private $memcached;
|
||||
private $initialTtl;
|
||||
/** @var bool */
|
||||
private $useExtendedReturn;
|
||||
|
||||
public static function isSupported()
|
||||
{
|
||||
return extension_loaded('memcached');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Memcached $memcached
|
||||
* @param int $initialTtl the expiration delay of locks in seconds
|
||||
*/
|
||||
public function __construct(\Memcached $memcached, $initialTtl = 300)
|
||||
{
|
||||
if (!static::isSupported()) {
|
||||
throw new InvalidArgumentException('Memcached extension is required');
|
||||
}
|
||||
|
||||
if ($initialTtl < 1) {
|
||||
throw new InvalidArgumentException(sprintf('%s() expects a strictly positive TTL. Got %d.', __METHOD__, $initialTtl));
|
||||
}
|
||||
|
||||
$this->memcached = $memcached;
|
||||
$this->initialTtl = $initialTtl;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(Key $key)
|
||||
{
|
||||
$token = $this->getToken($key);
|
||||
$key->reduceLifetime($this->initialTtl);
|
||||
if (!$this->memcached->add((string) $key, $token, (int) ceil($this->initialTtl))) {
|
||||
// the lock is already acquired. It could be us. Let's try to put off.
|
||||
$this->putOffExpiration($key, $this->initialTtl);
|
||||
}
|
||||
|
||||
if ($key->isExpired()) {
|
||||
throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $key));
|
||||
}
|
||||
}
|
||||
|
||||
public function waitAndSave(Key $key)
|
||||
{
|
||||
throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', get_class($this)));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function putOffExpiration(Key $key, $ttl)
|
||||
{
|
||||
if ($ttl < 1) {
|
||||
throw new InvalidArgumentException(sprintf('%s() expects a TTL greater or equals to 1. Got %s.', __METHOD__, $ttl));
|
||||
}
|
||||
|
||||
// Interface defines a float value but Store required an integer.
|
||||
$ttl = (int) ceil($ttl);
|
||||
|
||||
$token = $this->getToken($key);
|
||||
|
||||
list($value, $cas) = $this->getValueAndCas($key);
|
||||
|
||||
$key->reduceLifetime($ttl);
|
||||
// Could happens when we ask a putOff after a timeout but in luck nobody steal the lock
|
||||
if (\Memcached::RES_NOTFOUND === $this->memcached->getResultCode()) {
|
||||
if ($this->memcached->add((string) $key, $token, $ttl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// no luck, with concurrency, someone else acquire the lock
|
||||
throw new LockConflictedException();
|
||||
}
|
||||
|
||||
// Someone else steal the lock
|
||||
if ($value !== $token) {
|
||||
throw new LockConflictedException();
|
||||
}
|
||||
|
||||
if (!$this->memcached->cas($cas, (string) $key, $token, $ttl)) {
|
||||
throw new LockConflictedException();
|
||||
}
|
||||
|
||||
if ($key->isExpired()) {
|
||||
throw new LockExpiredException(sprintf('Failed to put off the expiration of the "%s" lock within the specified time.', $key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(Key $key)
|
||||
{
|
||||
$token = $this->getToken($key);
|
||||
|
||||
list($value, $cas) = $this->getValueAndCas($key);
|
||||
|
||||
if ($value !== $token) {
|
||||
// we are not the owner of the lock. Nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
// To avoid concurrency in deletion, the trick is to extends the TTL then deleting the key
|
||||
if (!$this->memcached->cas($cas, (string) $key, $token, 2)) {
|
||||
// Someone steal our lock. It does not belongs to us anymore. Nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
// Now, we are the owner of the lock for 2 more seconds, we can delete it.
|
||||
$this->memcached->delete((string) $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exists(Key $key)
|
||||
{
|
||||
return $this->memcached->get((string) $key) === $this->getToken($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an unique token for the given key.
|
||||
*
|
||||
* @param Key $key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getToken(Key $key)
|
||||
{
|
||||
if (!$key->hasState(__CLASS__)) {
|
||||
$token = base64_encode(random_bytes(32));
|
||||
$key->setState(__CLASS__, $token);
|
||||
}
|
||||
|
||||
return $key->getState(__CLASS__);
|
||||
}
|
||||
|
||||
private function getValueAndCas(Key $key)
|
||||
{
|
||||
if (null === $this->useExtendedReturn) {
|
||||
$this->useExtendedReturn = version_compare(phpversion('memcached'), '2.9.9', '>');
|
||||
}
|
||||
|
||||
if ($this->useExtendedReturn) {
|
||||
$extendedReturn = $this->memcached->get((string) $key, null, \Memcached::GET_EXTENDED);
|
||||
if (\Memcached::GET_ERROR_RETURN_VALUE === $extendedReturn) {
|
||||
return array($extendedReturn, 0.0);
|
||||
}
|
||||
|
||||
return array($extendedReturn['value'], $extendedReturn['cas']);
|
||||
}
|
||||
|
||||
$cas = 0.0;
|
||||
$value = $this->memcached->get((string) $key, null, $cas);
|
||||
|
||||
return array($value, $cas);
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Store;
|
||||
|
||||
use Symfony\Component\Cache\Traits\RedisProxy;
|
||||
use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||
use Symfony\Component\Lock\Exception\LockExpiredException;
|
||||
use Symfony\Component\Lock\Key;
|
||||
use Symfony\Component\Lock\StoreInterface;
|
||||
|
||||
/**
|
||||
* RedisStore is a StoreInterface implementation using Redis as store engine.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class RedisStore implements StoreInterface
|
||||
{
|
||||
private $redis;
|
||||
private $initialTtl;
|
||||
|
||||
/**
|
||||
* @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient
|
||||
* @param float $initialTtl the expiration delay of locks in seconds
|
||||
*/
|
||||
public function __construct($redisClient, $initialTtl = 300.0)
|
||||
{
|
||||
if (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \RedisCluster && !$redisClient instanceof \Predis\Client && !$redisClient instanceof RedisProxy) {
|
||||
throw new InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, is_object($redisClient) ? get_class($redisClient) : gettype($redisClient)));
|
||||
}
|
||||
|
||||
if ($initialTtl <= 0) {
|
||||
throw new InvalidArgumentException(sprintf('%s() expects a strictly positive TTL. Got %d.', __METHOD__, $initialTtl));
|
||||
}
|
||||
|
||||
$this->redis = $redisClient;
|
||||
$this->initialTtl = $initialTtl;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(Key $key)
|
||||
{
|
||||
$script = '
|
||||
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
||||
return redis.call("PEXPIRE", KEYS[1], ARGV[2])
|
||||
else
|
||||
return redis.call("set", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
|
||||
end
|
||||
';
|
||||
|
||||
$key->reduceLifetime($this->initialTtl);
|
||||
if (!$this->evaluate($script, (string) $key, array($this->getToken($key), (int) ceil($this->initialTtl * 1000)))) {
|
||||
throw new LockConflictedException();
|
||||
}
|
||||
|
||||
if ($key->isExpired()) {
|
||||
throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $key));
|
||||
}
|
||||
}
|
||||
|
||||
public function waitAndSave(Key $key)
|
||||
{
|
||||
throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', get_class($this)));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function putOffExpiration(Key $key, $ttl)
|
||||
{
|
||||
$script = '
|
||||
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
||||
return redis.call("PEXPIRE", KEYS[1], ARGV[2])
|
||||
else
|
||||
return 0
|
||||
end
|
||||
';
|
||||
|
||||
$key->reduceLifetime($ttl);
|
||||
if (!$this->evaluate($script, (string) $key, array($this->getToken($key), (int) ceil($ttl * 1000)))) {
|
||||
throw new LockConflictedException();
|
||||
}
|
||||
|
||||
if ($key->isExpired()) {
|
||||
throw new LockExpiredException(sprintf('Failed to put off the expiration of the "%s" lock within the specified time.', $key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(Key $key)
|
||||
{
|
||||
$script = '
|
||||
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
||||
return redis.call("DEL", KEYS[1])
|
||||
else
|
||||
return 0
|
||||
end
|
||||
';
|
||||
|
||||
$this->evaluate($script, (string) $key, array($this->getToken($key)));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exists(Key $key)
|
||||
{
|
||||
return $this->redis->get((string) $key) === $this->getToken($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates a script in the corresponding redis client.
|
||||
*
|
||||
* @param string $script
|
||||
* @param string $resource
|
||||
* @param array $args
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function evaluate($script, $resource, array $args)
|
||||
{
|
||||
if ($this->redis instanceof \Redis || $this->redis instanceof \RedisCluster || $this->redis instanceof RedisProxy) {
|
||||
return $this->redis->eval($script, array_merge(array($resource), $args), 1);
|
||||
}
|
||||
|
||||
if ($this->redis instanceof \RedisArray) {
|
||||
return $this->redis->_instance($this->redis->_target($resource))->eval($script, array_merge(array($resource), $args), 1);
|
||||
}
|
||||
|
||||
if ($this->redis instanceof \Predis\Client) {
|
||||
return call_user_func_array(array($this->redis, 'eval'), array_merge(array($script, 1, $resource), $args));
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(sprintf('%s() expects been initialized with a Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, is_object($this->redis) ? get_class($this->redis) : gettype($this->redis)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an unique token for the given key.
|
||||
*
|
||||
* @param Key $key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getToken(Key $key)
|
||||
{
|
||||
if (!$key->hasState(__CLASS__)) {
|
||||
$token = base64_encode(random_bytes(32));
|
||||
$key->setState(__CLASS__, $token);
|
||||
}
|
||||
|
||||
return $key->getState(__CLASS__);
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Store;
|
||||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||
use Symfony\Component\Lock\Key;
|
||||
use Symfony\Component\Lock\StoreInterface;
|
||||
|
||||
/**
|
||||
* RetryTillSaveStore is a StoreInterface implementation which decorate a non blocking StoreInterface to provide a
|
||||
* blocking storage.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class RetryTillSaveStore implements StoreInterface, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
private $decorated;
|
||||
private $retrySleep;
|
||||
private $retryCount;
|
||||
|
||||
/**
|
||||
* @param StoreInterface $decorated The decorated StoreInterface
|
||||
* @param int $retrySleep Duration in ms between 2 retry
|
||||
* @param int $retryCount Maximum amount of retry
|
||||
*/
|
||||
public function __construct(StoreInterface $decorated, $retrySleep = 100, $retryCount = PHP_INT_MAX)
|
||||
{
|
||||
$this->decorated = $decorated;
|
||||
$this->retrySleep = $retrySleep;
|
||||
$this->retryCount = $retryCount;
|
||||
|
||||
$this->logger = new NullLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(Key $key)
|
||||
{
|
||||
$this->decorated->save($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function waitAndSave(Key $key)
|
||||
{
|
||||
$retry = 0;
|
||||
$sleepRandomness = (int) ($this->retrySleep / 10);
|
||||
do {
|
||||
try {
|
||||
$this->decorated->save($key);
|
||||
|
||||
return;
|
||||
} catch (LockConflictedException $e) {
|
||||
usleep(($this->retrySleep + random_int(-$sleepRandomness, $sleepRandomness)) * 1000);
|
||||
}
|
||||
} while (++$retry < $this->retryCount);
|
||||
|
||||
$this->logger->warning('Failed to store the "{resource}" lock. Abort after {retry} retry.', array('resource' => $key, 'retry' => $retry));
|
||||
|
||||
throw new LockConflictedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function putOffExpiration(Key $key, $ttl)
|
||||
{
|
||||
$this->decorated->putOffExpiration($key, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(Key $key)
|
||||
{
|
||||
$this->decorated->delete($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exists(Key $key)
|
||||
{
|
||||
return $this->decorated->exists($key);
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Store;
|
||||
|
||||
use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||
use Symfony\Component\Lock\Exception\NotSupportedException;
|
||||
use Symfony\Component\Lock\Key;
|
||||
use Symfony\Component\Lock\StoreInterface;
|
||||
|
||||
/**
|
||||
* SemaphoreStore is a StoreInterface implementation using Semaphore as store engine.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class SemaphoreStore implements StoreInterface
|
||||
{
|
||||
/**
|
||||
* Returns whether or not the store is supported.
|
||||
*
|
||||
* @param bool|null $blocking when not null, checked again the blocking mode
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function isSupported($blocking = null)
|
||||
{
|
||||
if (!extension_loaded('sysvsem')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false === $blocking && \PHP_VERSION_ID < 50601) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (!static::isSupported()) {
|
||||
throw new InvalidArgumentException('Semaphore extension (sysvsem) is required');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(Key $key)
|
||||
{
|
||||
$this->lock($key, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function waitAndSave(Key $key)
|
||||
{
|
||||
$this->lock($key, true);
|
||||
}
|
||||
|
||||
private function lock(Key $key, $blocking)
|
||||
{
|
||||
if ($key->hasState(__CLASS__)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$resource = sem_get(crc32($key));
|
||||
|
||||
if (\PHP_VERSION_ID < 50601) {
|
||||
if (!$blocking) {
|
||||
throw new NotSupportedException(sprintf('The store "%s" does not supports non blocking locks.', get_class($this)));
|
||||
}
|
||||
|
||||
$acquired = sem_acquire($resource);
|
||||
} else {
|
||||
$acquired = sem_acquire($resource, !$blocking);
|
||||
}
|
||||
|
||||
if (!$acquired) {
|
||||
throw new LockConflictedException();
|
||||
}
|
||||
|
||||
$key->setState(__CLASS__, $resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(Key $key)
|
||||
{
|
||||
// The lock is maybe not acquired.
|
||||
if (!$key->hasState(__CLASS__)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$resource = $key->getState(__CLASS__);
|
||||
|
||||
sem_release($resource);
|
||||
|
||||
$key->removeState(__CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function putOffExpiration(Key $key, $ttl)
|
||||
{
|
||||
// do nothing, the semaphore locks forever.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exists(Key $key)
|
||||
{
|
||||
return $key->hasState(__CLASS__);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Store;
|
||||
|
||||
use Symfony\Component\Cache\Traits\RedisProxy;
|
||||
use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* StoreFactory create stores and connections.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class StoreFactory
|
||||
{
|
||||
/**
|
||||
* @param \Redis|\RedisArray|\RedisCluster|\Predis\Client|\Memcached $connection
|
||||
*
|
||||
* @return RedisStore|MemcachedStore
|
||||
*/
|
||||
public static function createStore($connection)
|
||||
{
|
||||
if ($connection instanceof \Redis || $connection instanceof \RedisArray || $connection instanceof \RedisCluster || $connection instanceof \Predis\Client || $connection instanceof RedisProxy) {
|
||||
return new RedisStore($connection);
|
||||
}
|
||||
if ($connection instanceof \Memcached) {
|
||||
return new MemcachedStore($connection);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(sprintf('Unsupported Connection: %s.', get_class($connection)));
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock;
|
||||
|
||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||
use Symfony\Component\Lock\Exception\NotSupportedException;
|
||||
|
||||
/**
|
||||
* StoreInterface defines an interface to manipulate a lock store.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
interface StoreInterface
|
||||
{
|
||||
/**
|
||||
* Stores the resource if it's not locked by someone else.
|
||||
*
|
||||
* @throws LockConflictedException
|
||||
*/
|
||||
public function save(Key $key);
|
||||
|
||||
/**
|
||||
* Waits until a key becomes free, then stores the resource.
|
||||
*
|
||||
* If the store does not support this feature it should throw a NotSupportedException.
|
||||
*
|
||||
* @throws LockConflictedException
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
public function waitAndSave(Key $key);
|
||||
|
||||
/**
|
||||
* Extends the ttl of a resource.
|
||||
*
|
||||
* If the store does not support this feature it should throw a NotSupportedException.
|
||||
*
|
||||
* @param float $ttl amount of second to keep the lock in the store
|
||||
*
|
||||
* @throws LockConflictedException
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
public function putOffExpiration(Key $key, $ttl);
|
||||
|
||||
/**
|
||||
* Removes a resource from the storage.
|
||||
*/
|
||||
public function delete(Key $key);
|
||||
|
||||
/**
|
||||
* Returns whether or not the resource exists in the storage.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(Key $key);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Strategy;
|
||||
|
||||
/**
|
||||
* ConsensusStrategy is a StrategyInterface implementation where strictly more than 50% items should be successful.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class ConsensusStrategy implements StrategyInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isMet($numberOfSuccess, $numberOfItems)
|
||||
{
|
||||
return $numberOfSuccess > ($numberOfItems / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function canBeMet($numberOfFailure, $numberOfItems)
|
||||
{
|
||||
return $numberOfFailure < ($numberOfItems / 2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Strategy;
|
||||
|
||||
/**
|
||||
* StrategyInterface defines an interface to indicate when a quorum is met and can be met.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
interface StrategyInterface
|
||||
{
|
||||
/**
|
||||
* Returns whether or not the quorum is met.
|
||||
*
|
||||
* @param int $numberOfSuccess
|
||||
* @param int $numberOfItems
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isMet($numberOfSuccess, $numberOfItems);
|
||||
|
||||
/**
|
||||
* Returns whether or not the quorum *could* be met.
|
||||
*
|
||||
* This method does not mean the quorum *would* be met for sure, but can be useful to stop a process early when you
|
||||
* known there is no chance to meet the quorum.
|
||||
*
|
||||
* @param int $numberOfFailure
|
||||
* @param int $numberOfItems
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canBeMet($numberOfFailure, $numberOfItems);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Strategy;
|
||||
|
||||
/**
|
||||
* UnanimousStrategy is a StrategyInterface implementation where 100% of elements should be successful.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class UnanimousStrategy implements StrategyInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isMet($numberOfSuccess, $numberOfItems)
|
||||
{
|
||||
return $numberOfSuccess === $numberOfItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function canBeMet($numberOfFailure, $numberOfItems)
|
||||
{
|
||||
return 0 === $numberOfFailure;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Lock\Factory;
|
||||
use Symfony\Component\Lock\LockInterface;
|
||||
use Symfony\Component\Lock\StoreInterface;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class FactoryTest extends TestCase
|
||||
{
|
||||
public function testCreateLock()
|
||||
{
|
||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
|
||||
$factory = new Factory($store);
|
||||
$factory->setLogger($logger);
|
||||
|
||||
$lock = $factory->createLock('foo');
|
||||
|
||||
$this->assertInstanceOf(LockInterface::class, $lock);
|
||||
}
|
||||
}
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||
use Symfony\Component\Lock\Key;
|
||||
use Symfony\Component\Lock\Lock;
|
||||
use Symfony\Component\Lock\StoreInterface;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class LockTest extends TestCase
|
||||
{
|
||||
public function testAcquireNoBlocking()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$lock = new Lock($key, $store);
|
||||
|
||||
$store
|
||||
->expects($this->once())
|
||||
->method('save');
|
||||
|
||||
$this->assertTrue($lock->acquire(false));
|
||||
}
|
||||
|
||||
public function testAcquireReturnsFalse()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$lock = new Lock($key, $store);
|
||||
|
||||
$store
|
||||
->expects($this->once())
|
||||
->method('save')
|
||||
->willThrowException(new LockConflictedException());
|
||||
|
||||
$this->assertFalse($lock->acquire(false));
|
||||
}
|
||||
|
||||
public function testAcquireBlocking()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$lock = new Lock($key, $store);
|
||||
|
||||
$store
|
||||
->expects($this->never())
|
||||
->method('save');
|
||||
$store
|
||||
->expects($this->once())
|
||||
->method('waitAndSave');
|
||||
|
||||
$this->assertTrue($lock->acquire(true));
|
||||
}
|
||||
|
||||
public function testAcquireSetsTtl()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$lock = new Lock($key, $store, 10);
|
||||
|
||||
$store
|
||||
->expects($this->once())
|
||||
->method('save');
|
||||
$store
|
||||
->expects($this->once())
|
||||
->method('putOffExpiration')
|
||||
->with($key, 10);
|
||||
|
||||
$lock->acquire();
|
||||
}
|
||||
|
||||
public function testRefresh()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$lock = new Lock($key, $store, 10);
|
||||
|
||||
$store
|
||||
->expects($this->once())
|
||||
->method('putOffExpiration')
|
||||
->with($key, 10);
|
||||
|
||||
$lock->refresh();
|
||||
}
|
||||
|
||||
public function testIsAquired()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$lock = new Lock($key, $store, 10);
|
||||
|
||||
$store
|
||||
->expects($this->any())
|
||||
->method('exists')
|
||||
->with($key)
|
||||
->will($this->onConsecutiveCalls(true, false));
|
||||
|
||||
$this->assertTrue($lock->isAcquired());
|
||||
}
|
||||
|
||||
public function testRelease()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$lock = new Lock($key, $store, 10);
|
||||
|
||||
$store
|
||||
->expects($this->once())
|
||||
->method('delete')
|
||||
->with($key);
|
||||
|
||||
$store
|
||||
->expects($this->once())
|
||||
->method('exists')
|
||||
->with($key)
|
||||
->willReturn(false);
|
||||
|
||||
$lock->release();
|
||||
}
|
||||
|
||||
public function testReleaseOnDestruction()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$lock = new Lock($key, $store, 10);
|
||||
|
||||
$store
|
||||
->method('exists')
|
||||
->willReturnOnConsecutiveCalls(array(true, false))
|
||||
;
|
||||
$store
|
||||
->expects($this->once())
|
||||
->method('delete')
|
||||
;
|
||||
|
||||
$lock->acquire(false);
|
||||
unset($lock);
|
||||
}
|
||||
|
||||
public function testNoAutoReleaseWhenNotConfigured()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$lock = new Lock($key, $store, 10, false);
|
||||
|
||||
$store
|
||||
->method('exists')
|
||||
->willReturnOnConsecutiveCalls(array(true, false))
|
||||
;
|
||||
$store
|
||||
->expects($this->never())
|
||||
->method('delete')
|
||||
;
|
||||
|
||||
$lock->acquire(false);
|
||||
unset($lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Lock\Exception\LockReleasingException
|
||||
*/
|
||||
public function testReleaseThrowsExceptionIfNotWellDeleted()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$lock = new Lock($key, $store, 10);
|
||||
|
||||
$store
|
||||
->expects($this->once())
|
||||
->method('delete')
|
||||
->with($key);
|
||||
|
||||
$store
|
||||
->expects($this->once())
|
||||
->method('exists')
|
||||
->with($key)
|
||||
->willReturn(true);
|
||||
|
||||
$lock->release();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Lock\Exception\LockReleasingException
|
||||
*/
|
||||
public function testReleaseThrowsAndLog()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
|
||||
$lock = new Lock($key, $store, 10, true);
|
||||
$lock->setLogger($logger);
|
||||
|
||||
$logger->expects($this->atLeastOnce())
|
||||
->method('notice')
|
||||
->with('Failed to release the "{resource}" lock.', array('resource' => $key));
|
||||
|
||||
$store
|
||||
->expects($this->once())
|
||||
->method('delete')
|
||||
->with($key);
|
||||
|
||||
$store
|
||||
->expects($this->once())
|
||||
->method('exists')
|
||||
->with($key)
|
||||
->willReturn(true);
|
||||
|
||||
$lock->release();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideExpiredDates
|
||||
*/
|
||||
public function testExpiration($ttls, $expected)
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$store = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$lock = new Lock($key, $store, 10);
|
||||
|
||||
foreach ($ttls as $ttl) {
|
||||
if (null === $ttl) {
|
||||
$key->resetLifetime();
|
||||
} else {
|
||||
$key->reduceLifetime($ttl);
|
||||
}
|
||||
}
|
||||
$this->assertSame($expected, $lock->isExpired());
|
||||
}
|
||||
|
||||
public function provideExpiredDates()
|
||||
{
|
||||
yield array(array(-0.1), true);
|
||||
yield array(array(0.1, -0.1), true);
|
||||
yield array(array(-0.1, 0.1), true);
|
||||
|
||||
yield array(array(), false);
|
||||
yield array(array(0.1), false);
|
||||
yield array(array(-0.1, null), false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests\Store;
|
||||
|
||||
use Symfony\Component\Lock\Store\RedisStore;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
abstract class AbstractRedisStoreTest extends AbstractStoreTest
|
||||
{
|
||||
use ExpiringStoreTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getClockDelay()
|
||||
{
|
||||
return 250000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a RedisConnection.
|
||||
*
|
||||
* @return \Redis|\RedisArray|\RedisCluster|\Predis\Client
|
||||
*/
|
||||
abstract protected function getRedisConnection();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStore()
|
||||
{
|
||||
return new RedisStore($this->getRedisConnection());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests\Store;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||
use Symfony\Component\Lock\Key;
|
||||
use Symfony\Component\Lock\StoreInterface;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
abstract class AbstractStoreTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @return StoreInterface
|
||||
*/
|
||||
abstract protected function getStore();
|
||||
|
||||
public function testSave()
|
||||
{
|
||||
$store = $this->getStore();
|
||||
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
|
||||
$this->assertFalse($store->exists($key));
|
||||
$store->save($key);
|
||||
$this->assertTrue($store->exists($key));
|
||||
$store->delete($key);
|
||||
$this->assertFalse($store->exists($key));
|
||||
}
|
||||
|
||||
public function testSaveWithDifferentResources()
|
||||
{
|
||||
$store = $this->getStore();
|
||||
|
||||
$key1 = new Key(uniqid(__METHOD__, true));
|
||||
$key2 = new Key(uniqid(__METHOD__, true));
|
||||
|
||||
$store->save($key1);
|
||||
$this->assertTrue($store->exists($key1));
|
||||
$this->assertFalse($store->exists($key2));
|
||||
|
||||
$store->save($key2);
|
||||
$this->assertTrue($store->exists($key1));
|
||||
$this->assertTrue($store->exists($key2));
|
||||
|
||||
$store->delete($key1);
|
||||
$this->assertFalse($store->exists($key1));
|
||||
$this->assertTrue($store->exists($key2));
|
||||
|
||||
$store->delete($key2);
|
||||
$this->assertFalse($store->exists($key1));
|
||||
$this->assertFalse($store->exists($key2));
|
||||
}
|
||||
|
||||
public function testSaveWithDifferentKeysOnSameResources()
|
||||
{
|
||||
$store = $this->getStore();
|
||||
|
||||
$resource = uniqid(__METHOD__, true);
|
||||
$key1 = new Key($resource);
|
||||
$key2 = new Key($resource);
|
||||
|
||||
$store->save($key1);
|
||||
$this->assertTrue($store->exists($key1));
|
||||
$this->assertFalse($store->exists($key2));
|
||||
|
||||
try {
|
||||
$store->save($key2);
|
||||
$this->fail('The store shouldn\'t save the second key');
|
||||
} catch (LockConflictedException $e) {
|
||||
}
|
||||
|
||||
// The failure of previous attempt should not impact the state of current locks
|
||||
$this->assertTrue($store->exists($key1));
|
||||
$this->assertFalse($store->exists($key2));
|
||||
|
||||
$store->delete($key1);
|
||||
$this->assertFalse($store->exists($key1));
|
||||
$this->assertFalse($store->exists($key2));
|
||||
|
||||
$store->save($key2);
|
||||
$this->assertFalse($store->exists($key1));
|
||||
$this->assertTrue($store->exists($key2));
|
||||
|
||||
$store->delete($key2);
|
||||
$this->assertFalse($store->exists($key1));
|
||||
$this->assertFalse($store->exists($key2));
|
||||
}
|
||||
|
||||
public function testSaveTwice()
|
||||
{
|
||||
$store = $this->getStore();
|
||||
|
||||
$resource = uniqid(__METHOD__, true);
|
||||
$key = new Key($resource);
|
||||
|
||||
$store->save($key);
|
||||
$store->save($key);
|
||||
// just asserts it don't throw an exception
|
||||
$this->addToAssertionCount(1);
|
||||
|
||||
$store->delete($key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests\Store;
|
||||
|
||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||
use Symfony\Component\Lock\Key;
|
||||
use Symfony\Component\Lock\StoreInterface;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
trait BlockingStoreTestTrait
|
||||
{
|
||||
/**
|
||||
* @see AbstractStoreTest::getStore()
|
||||
*/
|
||||
abstract protected function getStore();
|
||||
|
||||
/**
|
||||
* Tests blocking locks thanks to pcntl.
|
||||
*
|
||||
* This test is time sensible: the $clockDelay could be adjust.
|
||||
*
|
||||
* @requires extension pcntl
|
||||
* @requires function pcntl_sigwaitinfo
|
||||
*/
|
||||
public function testBlockingLocks()
|
||||
{
|
||||
// Amount a microsecond used to order async actions
|
||||
$clockDelay = 50000;
|
||||
|
||||
if (\PHP_VERSION_ID < 50600 || defined('HHVM_VERSION_ID')) {
|
||||
$this->markTestSkipped('The PHP engine does not keep resource in child forks');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var StoreInterface $store */
|
||||
$store = $this->getStore();
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$parentPID = posix_getpid();
|
||||
|
||||
// Block SIGHUP signal
|
||||
pcntl_sigprocmask(SIG_BLOCK, array(SIGHUP));
|
||||
|
||||
if ($childPID = pcntl_fork()) {
|
||||
// Wait the start of the child
|
||||
pcntl_sigwaitinfo(array(SIGHUP), $info);
|
||||
|
||||
try {
|
||||
// This call should failed given the lock should already by acquired by the child
|
||||
$store->save($key);
|
||||
$this->fail('The store saves a locked key.');
|
||||
} catch (LockConflictedException $e) {
|
||||
}
|
||||
|
||||
// send the ready signal to the child
|
||||
posix_kill($childPID, SIGHUP);
|
||||
|
||||
// This call should be blocked by the child #1
|
||||
$store->waitAndSave($key);
|
||||
$this->assertTrue($store->exists($key));
|
||||
$store->delete($key);
|
||||
|
||||
// Now, assert the child process worked well
|
||||
pcntl_waitpid($childPID, $status1);
|
||||
$this->assertSame(0, pcntl_wexitstatus($status1), 'The child process couldn\'t lock the resource');
|
||||
} else {
|
||||
// Block SIGHUP signal
|
||||
pcntl_sigprocmask(SIG_BLOCK, array(SIGHUP));
|
||||
try {
|
||||
$store->save($key);
|
||||
// send the ready signal to the parent
|
||||
posix_kill($parentPID, SIGHUP);
|
||||
|
||||
// Wait for the parent to be ready
|
||||
pcntl_sigwaitinfo(array(SIGHUP), $info);
|
||||
|
||||
// Wait ClockDelay to let parent assert to finish
|
||||
usleep($clockDelay);
|
||||
$store->delete($key);
|
||||
exit(0);
|
||||
} catch (\Exception $e) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests\Store;
|
||||
|
||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||
use Symfony\Component\Lock\Key;
|
||||
use Symfony\Component\Lock\Strategy\UnanimousStrategy;
|
||||
use Symfony\Component\Lock\Strategy\StrategyInterface;
|
||||
use Symfony\Component\Lock\Store\CombinedStore;
|
||||
use Symfony\Component\Lock\Store\RedisStore;
|
||||
use Symfony\Component\Lock\StoreInterface;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class CombinedStoreTest extends AbstractStoreTest
|
||||
{
|
||||
use ExpiringStoreTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getClockDelay()
|
||||
{
|
||||
return 250000;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStore()
|
||||
{
|
||||
$redis = new \Predis\Client('tcp://'.getenv('REDIS_HOST').':6379');
|
||||
try {
|
||||
$redis->connect();
|
||||
} catch (\Exception $e) {
|
||||
self::markTestSkipped($e->getMessage());
|
||||
}
|
||||
|
||||
return new CombinedStore(array(new RedisStore($redis)), new UnanimousStrategy());
|
||||
}
|
||||
|
||||
/** @var \PHPUnit_Framework_MockObject_MockObject */
|
||||
private $strategy;
|
||||
/** @var \PHPUnit_Framework_MockObject_MockObject */
|
||||
private $store1;
|
||||
/** @var \PHPUnit_Framework_MockObject_MockObject */
|
||||
private $store2;
|
||||
/** @var CombinedStore */
|
||||
private $store;
|
||||
|
||||
public function setup()
|
||||
{
|
||||
$this->strategy = $this->getMockBuilder(StrategyInterface::class)->getMock();
|
||||
$this->store1 = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$this->store2 = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
|
||||
$this->store = new CombinedStore(array($this->store1, $this->store2), $this->strategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Lock\Exception\LockConflictedException
|
||||
*/
|
||||
public function testSaveThrowsExceptionOnFailure()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
|
||||
$this->store1
|
||||
->expects($this->once())
|
||||
->method('save')
|
||||
->with($key)
|
||||
->willThrowException(new LockConflictedException());
|
||||
$this->store2
|
||||
->expects($this->once())
|
||||
->method('save')
|
||||
->with($key)
|
||||
->willThrowException(new LockConflictedException());
|
||||
|
||||
$this->strategy
|
||||
->expects($this->any())
|
||||
->method('canBeMet')
|
||||
->willReturn(true);
|
||||
$this->strategy
|
||||
->expects($this->any())
|
||||
->method('isMet')
|
||||
->willReturn(false);
|
||||
|
||||
$this->store->save($key);
|
||||
}
|
||||
|
||||
public function testSaveCleanupOnFailure()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
|
||||
$this->store1
|
||||
->expects($this->once())
|
||||
->method('save')
|
||||
->with($key)
|
||||
->willThrowException(new LockConflictedException());
|
||||
$this->store2
|
||||
->expects($this->once())
|
||||
->method('save')
|
||||
->with($key)
|
||||
->willThrowException(new LockConflictedException());
|
||||
|
||||
$this->store1
|
||||
->expects($this->once())
|
||||
->method('delete');
|
||||
$this->store2
|
||||
->expects($this->once())
|
||||
->method('delete');
|
||||
|
||||
$this->strategy
|
||||
->expects($this->any())
|
||||
->method('canBeMet')
|
||||
->willReturn(true);
|
||||
$this->strategy
|
||||
->expects($this->any())
|
||||
->method('isMet')
|
||||
->willReturn(false);
|
||||
|
||||
try {
|
||||
$this->store->save($key);
|
||||
} catch (LockConflictedException $e) {
|
||||
// Catch the exception given this is not what we want to assert in this tests
|
||||
}
|
||||
}
|
||||
|
||||
public function testSaveAbortWhenStrategyCantBeMet()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
|
||||
$this->store1
|
||||
->expects($this->once())
|
||||
->method('save')
|
||||
->with($key)
|
||||
->willThrowException(new LockConflictedException());
|
||||
$this->store2
|
||||
->expects($this->never())
|
||||
->method('save');
|
||||
|
||||
$this->strategy
|
||||
->expects($this->once())
|
||||
->method('canBeMet')
|
||||
->willReturn(false);
|
||||
$this->strategy
|
||||
->expects($this->any())
|
||||
->method('isMet')
|
||||
->willReturn(false);
|
||||
|
||||
try {
|
||||
$this->store->save($key);
|
||||
} catch (LockConflictedException $e) {
|
||||
// Catch the exception given this is not what we want to assert in this tests
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Lock\Exception\LockConflictedException
|
||||
*/
|
||||
public function testputOffExpirationThrowsExceptionOnFailure()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$ttl = random_int(1, 10);
|
||||
|
||||
$this->store1
|
||||
->expects($this->once())
|
||||
->method('putOffExpiration')
|
||||
->with($key, $this->lessThanOrEqual($ttl))
|
||||
->willThrowException(new LockConflictedException());
|
||||
$this->store2
|
||||
->expects($this->once())
|
||||
->method('putOffExpiration')
|
||||
->with($key, $this->lessThanOrEqual($ttl))
|
||||
->willThrowException(new LockConflictedException());
|
||||
|
||||
$this->strategy
|
||||
->expects($this->any())
|
||||
->method('canBeMet')
|
||||
->willReturn(true);
|
||||
$this->strategy
|
||||
->expects($this->any())
|
||||
->method('isMet')
|
||||
->willReturn(false);
|
||||
|
||||
$this->store->putOffExpiration($key, $ttl);
|
||||
}
|
||||
|
||||
public function testputOffExpirationCleanupOnFailure()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$ttl = random_int(1, 10);
|
||||
|
||||
$this->store1
|
||||
->expects($this->once())
|
||||
->method('putOffExpiration')
|
||||
->with($key, $this->lessThanOrEqual($ttl))
|
||||
->willThrowException(new LockConflictedException());
|
||||
$this->store2
|
||||
->expects($this->once())
|
||||
->method('putOffExpiration')
|
||||
->with($key, $this->lessThanOrEqual($ttl))
|
||||
->willThrowException(new LockConflictedException());
|
||||
|
||||
$this->store1
|
||||
->expects($this->once())
|
||||
->method('delete');
|
||||
$this->store2
|
||||
->expects($this->once())
|
||||
->method('delete');
|
||||
|
||||
$this->strategy
|
||||
->expects($this->any())
|
||||
->method('canBeMet')
|
||||
->willReturn(true);
|
||||
$this->strategy
|
||||
->expects($this->any())
|
||||
->method('isMet')
|
||||
->willReturn(false);
|
||||
|
||||
try {
|
||||
$this->store->putOffExpiration($key, $ttl);
|
||||
} catch (LockConflictedException $e) {
|
||||
// Catch the exception given this is not what we want to assert in this tests
|
||||
}
|
||||
}
|
||||
|
||||
public function testputOffExpirationAbortWhenStrategyCantBeMet()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$ttl = random_int(1, 10);
|
||||
|
||||
$this->store1
|
||||
->expects($this->once())
|
||||
->method('putOffExpiration')
|
||||
->with($key, $this->lessThanOrEqual($ttl))
|
||||
->willThrowException(new LockConflictedException());
|
||||
$this->store2
|
||||
->expects($this->never())
|
||||
->method('putOffExpiration');
|
||||
|
||||
$this->strategy
|
||||
->expects($this->once())
|
||||
->method('canBeMet')
|
||||
->willReturn(false);
|
||||
$this->strategy
|
||||
->expects($this->any())
|
||||
->method('isMet')
|
||||
->willReturn(false);
|
||||
|
||||
try {
|
||||
$this->store->putOffExpiration($key, $ttl);
|
||||
} catch (LockConflictedException $e) {
|
||||
// Catch the exception given this is not what we want to assert in this tests
|
||||
}
|
||||
}
|
||||
|
||||
public function testPutOffExpirationIgnoreNonExpiringStorage()
|
||||
{
|
||||
$store1 = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
$store2 = $this->getMockBuilder(StoreInterface::class)->getMock();
|
||||
|
||||
$store = new CombinedStore(array($store1, $store2), $this->strategy);
|
||||
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$ttl = random_int(1, 10);
|
||||
|
||||
$this->strategy
|
||||
->expects($this->any())
|
||||
->method('canBeMet')
|
||||
->willReturn(true);
|
||||
$this->strategy
|
||||
->expects($this->once())
|
||||
->method('isMet')
|
||||
->with(2, 2)
|
||||
->willReturn(true);
|
||||
|
||||
$store->putOffExpiration($key, $ttl);
|
||||
}
|
||||
|
||||
public function testExistsDontAskToEveryBody()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
|
||||
$this->store1
|
||||
->expects($this->any())
|
||||
->method('exists')
|
||||
->with($key)
|
||||
->willReturn(false);
|
||||
$this->store2
|
||||
->expects($this->never())
|
||||
->method('exists');
|
||||
|
||||
$this->strategy
|
||||
->expects($this->any())
|
||||
->method('canBeMet')
|
||||
->willReturn(true);
|
||||
$this->strategy
|
||||
->expects($this->once())
|
||||
->method('isMet')
|
||||
->willReturn(true);
|
||||
|
||||
$this->assertTrue($this->store->exists($key));
|
||||
}
|
||||
|
||||
public function testExistsAbortWhenStrategyCantBeMet()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
|
||||
$this->store1
|
||||
->expects($this->any())
|
||||
->method('exists')
|
||||
->with($key)
|
||||
->willReturn(false);
|
||||
$this->store2
|
||||
->expects($this->never())
|
||||
->method('exists');
|
||||
|
||||
$this->strategy
|
||||
->expects($this->once())
|
||||
->method('canBeMet')
|
||||
->willReturn(false);
|
||||
$this->strategy
|
||||
->expects($this->once())
|
||||
->method('isMet')
|
||||
->willReturn(false);
|
||||
|
||||
$this->assertFalse($this->store->exists($key));
|
||||
}
|
||||
|
||||
public function testDeleteDontStopOnFailure()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
|
||||
$this->store1
|
||||
->expects($this->once())
|
||||
->method('delete')
|
||||
->with($key)
|
||||
->willThrowException(new \Exception());
|
||||
$this->store2
|
||||
->expects($this->once())
|
||||
->method('delete')
|
||||
->with($key);
|
||||
|
||||
$this->store->delete($key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests\Store;
|
||||
|
||||
use Symfony\Component\Lock\Key;
|
||||
use Symfony\Component\Lock\StoreInterface;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
trait ExpiringStoreTestTrait
|
||||
{
|
||||
/**
|
||||
* Amount a microsecond used to order async actions.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract protected function getClockDelay();
|
||||
|
||||
/**
|
||||
* @see AbstractStoreTest::getStore()
|
||||
*/
|
||||
abstract protected function getStore();
|
||||
|
||||
/**
|
||||
* Tests the store automatically delete the key when it expire.
|
||||
*
|
||||
* This test is time sensible: the $clockDelay could be adjust.
|
||||
*/
|
||||
public function testExpiration()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
$clockDelay = $this->getClockDelay();
|
||||
|
||||
/** @var StoreInterface $store */
|
||||
$store = $this->getStore();
|
||||
|
||||
$store->save($key);
|
||||
$store->putOffExpiration($key, $clockDelay / 1000000);
|
||||
$this->assertTrue($store->exists($key));
|
||||
|
||||
usleep(2 * $clockDelay);
|
||||
$this->assertFalse($store->exists($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the store thrown exception when TTL expires.
|
||||
*
|
||||
* @expectedException \Symfony\Component\Lock\Exception\LockExpiredException
|
||||
*/
|
||||
public function testAbortAfterExpiration()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
|
||||
/** @var StoreInterface $store */
|
||||
$store = $this->getStore();
|
||||
|
||||
$store->save($key);
|
||||
$store->putOffExpiration($key, 1 / 1000000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the refresh can push the limits to the expiration.
|
||||
*
|
||||
* This test is time sensible: the $clockDelay could be adjust.
|
||||
*/
|
||||
public function testRefreshLock()
|
||||
{
|
||||
// Amount a microsecond used to order async actions
|
||||
$clockDelay = $this->getClockDelay();
|
||||
|
||||
// Amount a microsecond used to order async actions
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
|
||||
/** @var StoreInterface $store */
|
||||
$store = $this->getStore();
|
||||
|
||||
$store->save($key);
|
||||
$store->putOffExpiration($key, $clockDelay / 1000000);
|
||||
$this->assertTrue($store->exists($key));
|
||||
|
||||
usleep(2 * $clockDelay);
|
||||
$this->assertFalse($store->exists($key));
|
||||
}
|
||||
|
||||
public function testSetExpiration()
|
||||
{
|
||||
$key = new Key(uniqid(__METHOD__, true));
|
||||
|
||||
/** @var StoreInterface $store */
|
||||
$store = $this->getStore();
|
||||
|
||||
$store->save($key);
|
||||
$store->putOffExpiration($key, 1);
|
||||
$this->assertGreaterThanOrEqual(0, $key->getRemainingLifetime());
|
||||
$this->assertLessThanOrEqual(1, $key->getRemainingLifetime());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests\Store;
|
||||
|
||||
use Symfony\Component\Lock\Key;
|
||||
use Symfony\Component\Lock\Store\FlockStore;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class FlockStoreTest extends AbstractStoreTest
|
||||
{
|
||||
use BlockingStoreTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getStore()
|
||||
{
|
||||
return new FlockStore();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Lock\Exception\InvalidArgumentException
|
||||
* @expectedExceptionMessage The directory "/a/b/c/d/e" is not writable.
|
||||
*/
|
||||
public function testConstructWhenRepositoryDoesNotExist()
|
||||
{
|
||||
if (!getenv('USER') || 'root' === getenv('USER')) {
|
||||
$this->markTestSkipped('This test will fail if run under superuser');
|
||||
}
|
||||
|
||||
new FlockStore('/a/b/c/d/e');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Lock\Exception\InvalidArgumentException
|
||||
* @expectedExceptionMessage The directory "/" is not writable.
|
||||
*/
|
||||
public function testConstructWhenRepositoryIsNotWriteable()
|
||||
{
|
||||
if (!getenv('USER') || 'root' === getenv('USER')) {
|
||||
$this->markTestSkipped('This test will fail if run under superuser');
|
||||
}
|
||||
|
||||
new FlockStore('/');
|
||||
}
|
||||
|
||||
public function testSaveSanitizeName()
|
||||
{
|
||||
$store = $this->getStore();
|
||||
|
||||
$key = new Key('<?php echo "% hello word ! %" ?>');
|
||||
|
||||
$file = sprintf(
|
||||
'%s/sf.-php-echo-hello-word-.%s.lock',
|
||||
sys_get_temp_dir(),
|
||||
strtr(substr(base64_encode(hash('sha256', $key, true)), 0, 7), '/', '_')
|
||||
);
|
||||
// ensure the file does not exist before the store
|
||||
@unlink($file);
|
||||
|
||||
$store->save($key);
|
||||
|
||||
$this->assertFileExists($file);
|
||||
|
||||
$store->delete($key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests\Store;
|
||||
|
||||
use Symfony\Component\Lock\Store\MemcachedStore;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*
|
||||
* @requires extension memcached
|
||||
*/
|
||||
class MemcachedStoreTest extends AbstractStoreTest
|
||||
{
|
||||
use ExpiringStoreTestTrait;
|
||||
|
||||
public static function setupBeforeClass()
|
||||
{
|
||||
$memcached = new \Memcached();
|
||||
$memcached->addServer(getenv('MEMCACHED_HOST'), 11211);
|
||||
$memcached->get('foo');
|
||||
$code = $memcached->getResultCode();
|
||||
|
||||
if (\Memcached::RES_SUCCESS !== $code && \Memcached::RES_NOTFOUND !== $code) {
|
||||
self::markTestSkipped('Unable to connect to the memcache host');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getClockDelay()
|
||||
{
|
||||
return 1000000;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStore()
|
||||
{
|
||||
$memcached = new \Memcached();
|
||||
$memcached->addServer(getenv('MEMCACHED_HOST'), 11211);
|
||||
|
||||
return new MemcachedStore($memcached);
|
||||
}
|
||||
|
||||
public function testAbortAfterExpiration()
|
||||
{
|
||||
$this->markTestSkipped('Memcached expects a TTL greater than 1 sec. Simulating a slow network is too hard');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests\Store;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class PredisStoreTest extends AbstractRedisStoreTest
|
||||
{
|
||||
public static function setupBeforeClass()
|
||||
{
|
||||
$redis = new \Predis\Client('tcp://'.getenv('REDIS_HOST').':6379');
|
||||
try {
|
||||
$redis->connect();
|
||||
} catch (\Exception $e) {
|
||||
self::markTestSkipped($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function getRedisConnection()
|
||||
{
|
||||
$redis = new \Predis\Client('tcp://'.getenv('REDIS_HOST').':6379');
|
||||
$redis->connect();
|
||||
|
||||
return $redis;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests\Store;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*
|
||||
* @requires extension redis
|
||||
*/
|
||||
class RedisArrayStoreTest extends AbstractRedisStoreTest
|
||||
{
|
||||
public static function setupBeforeClass()
|
||||
{
|
||||
if (!class_exists('RedisArray')) {
|
||||
self::markTestSkipped('The RedisArray class is required.');
|
||||
}
|
||||
if (!@((new \Redis())->connect(getenv('REDIS_HOST')))) {
|
||||
$e = error_get_last();
|
||||
self::markTestSkipped($e['message']);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getRedisConnection()
|
||||
{
|
||||
$redis = new \RedisArray(array(getenv('REDIS_HOST')));
|
||||
|
||||
return $redis;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests\Store;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*
|
||||
* @requires extension redis
|
||||
*/
|
||||
class RedisStoreTest extends AbstractRedisStoreTest
|
||||
{
|
||||
public static function setupBeforeClass()
|
||||
{
|
||||
if (!@((new \Redis())->connect(getenv('REDIS_HOST')))) {
|
||||
$e = error_get_last();
|
||||
self::markTestSkipped($e['message']);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getRedisConnection()
|
||||
{
|
||||
$redis = new \Redis();
|
||||
$redis->connect(getenv('REDIS_HOST'));
|
||||
|
||||
return $redis;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests\Store;
|
||||
|
||||
use Symfony\Component\Lock\Store\RedisStore;
|
||||
use Symfony\Component\Lock\Store\RetryTillSaveStore;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class RetryTillSaveStoreTest extends AbstractStoreTest
|
||||
{
|
||||
use BlockingStoreTestTrait;
|
||||
|
||||
public function getStore()
|
||||
{
|
||||
$redis = new \Predis\Client('tcp://'.getenv('REDIS_HOST').':6379');
|
||||
try {
|
||||
$redis->connect();
|
||||
} catch (\Exception $e) {
|
||||
self::markTestSkipped($e->getMessage());
|
||||
}
|
||||
|
||||
return new RetryTillSaveStore(new RedisStore($redis), 100, 100);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests\Store;
|
||||
|
||||
use Symfony\Component\Lock\Store\SemaphoreStore;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*
|
||||
* @requires extension sysvsem
|
||||
*/
|
||||
class SemaphoreStoreTest extends AbstractStoreTest
|
||||
{
|
||||
use BlockingStoreTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getStore()
|
||||
{
|
||||
if (\PHP_VERSION_ID < 50601) {
|
||||
$this->markTestSkipped('Non blocking semaphore are supported by PHP version greater or equals than 5.6.1');
|
||||
}
|
||||
|
||||
return new SemaphoreStore();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests\Strategy;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Lock\Strategy\ConsensusStrategy;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class ConsensusStrategyTest extends TestCase
|
||||
{
|
||||
/** @var ConsensusStrategy */
|
||||
private $strategy;
|
||||
|
||||
public function setup()
|
||||
{
|
||||
$this->strategy = new ConsensusStrategy();
|
||||
}
|
||||
|
||||
public function provideMetResults()
|
||||
{
|
||||
// success, failure, total, isMet
|
||||
yield array(3, 0, 3, true);
|
||||
yield array(2, 1, 3, true);
|
||||
yield array(2, 0, 3, true);
|
||||
yield array(1, 2, 3, false);
|
||||
yield array(1, 1, 3, false);
|
||||
yield array(1, 0, 3, false);
|
||||
yield array(0, 3, 3, false);
|
||||
yield array(0, 2, 3, false);
|
||||
yield array(0, 1, 3, false);
|
||||
yield array(0, 0, 3, false);
|
||||
|
||||
yield array(2, 0, 2, true);
|
||||
yield array(1, 1, 2, false);
|
||||
yield array(1, 0, 2, false);
|
||||
yield array(0, 2, 2, false);
|
||||
yield array(0, 1, 2, false);
|
||||
yield array(0, 0, 2, false);
|
||||
}
|
||||
|
||||
public function provideIndeterminate()
|
||||
{
|
||||
// success, failure, total, canBeMet
|
||||
yield array(3, 0, 3, true);
|
||||
yield array(2, 1, 3, true);
|
||||
yield array(2, 0, 3, true);
|
||||
yield array(1, 2, 3, false);
|
||||
yield array(1, 1, 3, true);
|
||||
yield array(1, 0, 3, true);
|
||||
yield array(0, 3, 3, false);
|
||||
yield array(0, 2, 3, false);
|
||||
yield array(0, 1, 3, true);
|
||||
yield array(0, 0, 3, true);
|
||||
|
||||
yield array(2, 0, 2, true);
|
||||
yield array(1, 1, 2, false);
|
||||
yield array(1, 0, 2, true);
|
||||
yield array(0, 2, 2, false);
|
||||
yield array(0, 1, 2, false);
|
||||
yield array(0, 0, 2, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideMetResults
|
||||
*/
|
||||
public function testMet($success, $failure, $total, $isMet)
|
||||
{
|
||||
$this->assertSame($isMet, $this->strategy->isMet($success, $total));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideIndeterminate
|
||||
*/
|
||||
public function testCanBeMet($success, $failure, $total, $isMet)
|
||||
{
|
||||
$this->assertSame($isMet, $this->strategy->canBeMet($failure, $total));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Lock\Tests\Strategy;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Lock\Strategy\UnanimousStrategy;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class UnanimousStrategyTest extends TestCase
|
||||
{
|
||||
/** @var UnanimousStrategy */
|
||||
private $strategy;
|
||||
|
||||
public function setup()
|
||||
{
|
||||
$this->strategy = new UnanimousStrategy();
|
||||
}
|
||||
|
||||
public function provideMetResults()
|
||||
{
|
||||
// success, failure, total, isMet
|
||||
yield array(3, 0, 3, true);
|
||||
yield array(2, 1, 3, false);
|
||||
yield array(2, 0, 3, false);
|
||||
yield array(1, 2, 3, false);
|
||||
yield array(1, 1, 3, false);
|
||||
yield array(1, 0, 3, false);
|
||||
yield array(0, 3, 3, false);
|
||||
yield array(0, 2, 3, false);
|
||||
yield array(0, 1, 3, false);
|
||||
yield array(0, 0, 3, false);
|
||||
|
||||
yield array(2, 0, 2, true);
|
||||
yield array(1, 1, 2, false);
|
||||
yield array(1, 0, 2, false);
|
||||
yield array(0, 2, 2, false);
|
||||
yield array(0, 1, 2, false);
|
||||
yield array(0, 0, 2, false);
|
||||
}
|
||||
|
||||
public function provideIndeterminate()
|
||||
{
|
||||
// success, failure, total, canBeMet
|
||||
yield array(3, 0, 3, true);
|
||||
yield array(2, 1, 3, false);
|
||||
yield array(2, 0, 3, true);
|
||||
yield array(1, 2, 3, false);
|
||||
yield array(1, 1, 3, false);
|
||||
yield array(1, 0, 3, true);
|
||||
yield array(0, 3, 3, false);
|
||||
yield array(0, 2, 3, false);
|
||||
yield array(0, 1, 3, false);
|
||||
yield array(0, 0, 3, true);
|
||||
|
||||
yield array(2, 0, 2, true);
|
||||
yield array(1, 1, 2, false);
|
||||
yield array(1, 0, 2, true);
|
||||
yield array(0, 2, 2, false);
|
||||
yield array(0, 1, 2, false);
|
||||
yield array(0, 0, 2, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideMetResults
|
||||
*/
|
||||
public function testMet($success, $failure, $total, $isMet)
|
||||
{
|
||||
$this->assertSame($isMet, $this->strategy->isMet($success, $total));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideIndeterminate
|
||||
*/
|
||||
public function testCanBeMet($success, $failure, $total, $isMet)
|
||||
{
|
||||
$this->assertSame($isMet, $this->strategy->canBeMet($failure, $total));
|
||||
}
|
||||
}
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "symfony/lock",
|
||||
"type": "library",
|
||||
"description": "Symfony Lock Component",
|
||||
"keywords": ["locking", "redlock", "mutex", "semaphore", "flock", "cas"],
|
||||
"homepage": "https://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jérémy Derussé",
|
||||
"email": "jeremy@derusse.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^5.5.9|>=7.0.8",
|
||||
"symfony/polyfill-php70": "~1.0",
|
||||
"psr/log": "~1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"predis/predis": "~1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\Lock\\": "" },
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.4-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
bootstrap="vendor/autoload.php"
|
||||
failOnRisky="true"
|
||||
failOnWarning="true"
|
||||
>
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1" />
|
||||
<env name="REDIS_HOST" value="localhost" />
|
||||
<env name="MEMCACHED_HOST" value="localhost" />
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Symfony Lock Component Test Suite">
|
||||
<directory>./Tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./</directory>
|
||||
<exclude>
|
||||
<directory>./Tests</directory>
|
||||
<directory>./vendor</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2015-2018 Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Polyfill\Php70;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Php70
|
||||
{
|
||||
public static function intdiv($dividend, $divisor)
|
||||
{
|
||||
$dividend = self::intArg($dividend, __FUNCTION__, 1);
|
||||
$divisor = self::intArg($divisor, __FUNCTION__, 2);
|
||||
|
||||
if (0 === $divisor) {
|
||||
throw new \DivisionByZeroError('Division by zero');
|
||||
}
|
||||
if (-1 === $divisor && ~PHP_INT_MAX === $dividend) {
|
||||
throw new \ArithmeticError('Division of PHP_INT_MIN by -1 is not an integer');
|
||||
}
|
||||
|
||||
return ($dividend - ($dividend % $divisor)) / $divisor;
|
||||
}
|
||||
|
||||
public static function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0)
|
||||
{
|
||||
$count = 0;
|
||||
$result = (string) $subject;
|
||||
if (0 === $limit = self::intArg($limit, __FUNCTION__, 3)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ($patterns as $pattern => $callback) {
|
||||
$result = preg_replace_callback($pattern, $callback, $result, $limit, $c);
|
||||
$count += $c;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function error_clear_last()
|
||||
{
|
||||
static $handler;
|
||||
if (!$handler) {
|
||||
$handler = function() { return false; };
|
||||
}
|
||||
set_error_handler($handler);
|
||||
@trigger_error('');
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
private static function intArg($value, $caller, $pos)
|
||||
{
|
||||
if (\is_int($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (!\is_numeric($value) || PHP_INT_MAX <= ($value += 0) || ~PHP_INT_MAX >= $value) {
|
||||
throw new \TypeError(sprintf('%s() expects parameter %d to be integer, %s given', $caller, $pos, gettype($value)));
|
||||
}
|
||||
|
||||
return (int) $value;
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
Symfony Polyfill / Php70
|
||||
========================
|
||||
|
||||
This component provides features unavailable in releases prior to PHP 7.0:
|
||||
|
||||
- [`intdiv`](http://php.net/intdiv)
|
||||
- [`preg_replace_callback_array`](http://php.net/preg_replace_callback_array)
|
||||
- [`error_clear_last`](http://php.net/error_clear_last)
|
||||
- `random_bytes` and `random_int` (from [paragonie/random_compat](https://github.com/paragonie/random_compat))
|
||||
- [`*Error` throwable classes](http://php.net/Error)
|
||||
- [`PHP_INT_MIN`](http://php.net/manual/en/reserved.constants.php#constant.php-int-min)
|
||||
- `SessionUpdateTimestampHandlerInterface`
|
||||
|
||||
More information can be found in the
|
||||
[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
|
||||
|
||||
Compatibility notes
|
||||
===================
|
||||
|
||||
To write portable code between PHP5 and PHP7, some care must be taken:
|
||||
- `\*Error` exceptions must be caught before `\Exception`;
|
||||
- after calling `error_clear_last()`, the result of `$e = error_get_last()` must be
|
||||
verified using `isset($e['message'][0])` instead of `null !== $e`.
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
This library is released under the [MIT license](LICENSE).
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
class ArithmeticError extends Error
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
class AssertionError extends Error
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
class DivisionByZeroError extends Error
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
class Error extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
class ParseError extends Error
|
||||
{
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
interface SessionUpdateTimestampHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Checks if a session identifier already exists or not.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validateId($key);
|
||||
|
||||
/**
|
||||
* Updates the timestamp of a session when its data didn't change.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $val
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateTimestamp($key, $val);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
class TypeError extends Error
|
||||
{
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Symfony\Polyfill\Php70 as p;
|
||||
|
||||
if (PHP_VERSION_ID < 70000) {
|
||||
if (!defined('PHP_INT_MIN')) {
|
||||
define('PHP_INT_MIN', ~PHP_INT_MAX);
|
||||
}
|
||||
if (!function_exists('intdiv')) {
|
||||
function intdiv($dividend, $divisor) { return p\Php70::intdiv($dividend, $divisor); }
|
||||
}
|
||||
if (!function_exists('preg_replace_callback_array')) {
|
||||
function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0) { return p\Php70::preg_replace_callback_array($patterns, $subject, $limit, $count); }
|
||||
}
|
||||
if (!function_exists('error_clear_last')) {
|
||||
function error_clear_last() { return p\Php70::error_clear_last(); }
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "symfony/polyfill-php70",
|
||||
"type": "library",
|
||||
"description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
|
||||
"keywords": ["polyfill", "shim", "compatibility", "portable"],
|
||||
"homepage": "https://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.3",
|
||||
"paragonie/random_compat": "~1.0|~2.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Polyfill\\Php70\\": "" },
|
||||
"files": [ "bootstrap.php" ],
|
||||
"classmap": [ "Resources/stubs" ]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.8-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user