This commit is contained in:
foobar
2022-08-21 21:39:06 +02:00
commit 27c1969aaa
7354 changed files with 897064 additions and 0 deletions

12
vendor/autoload.php vendored Normal file
View File

@@ -0,0 +1,12 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
exit(1);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit10d874da83cca5c26b320b144519f023::getLoader();

23
vendor/bin/carbon vendored Executable file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/env php
<?php
use Carbon\Cli\Invoker;
$dir = __DIR__.'/..';
if (!file_exists($dir.'/autoload.php')) {
$dir = __DIR__.'/../vendor';
}
if (!file_exists($dir.'/autoload.php')) {
$dir = __DIR__.'/../../..';
}
if (!file_exists($dir.'/autoload.php')) {
echo 'Autoload not found.';
exit(1);
}
require $dir.'/autoload.php';
exit((new Invoker())(...$argv) ? 0 : 1);

94
vendor/bin/patch-type-declarations vendored Executable file
View File

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

205
vendor/bin/php-parse vendored Executable file
View File

@@ -0,0 +1,205 @@
#!/usr/bin/env php
<?php
foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../vendor/autoload.php'] as $file) {
if (file_exists($file)) {
require $file;
break;
}
}
ini_set('xdebug.max_nesting_level', 3000);
// Disable Xdebug var_dump() output truncation
ini_set('xdebug.var_display_max_children', -1);
ini_set('xdebug.var_display_max_data', -1);
ini_set('xdebug.var_display_max_depth', -1);
list($operations, $files, $attributes) = parseArgs($argv);
/* Dump nodes by default */
if (empty($operations)) {
$operations[] = 'dump';
}
if (empty($files)) {
showHelp("Must specify at least one file.");
}
$lexer = new PhpParser\Lexer\Emulative(['usedAttributes' => [
'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments'
]]);
$parser = (new PhpParser\ParserFactory)->create(
PhpParser\ParserFactory::PREFER_PHP7,
$lexer
);
$dumper = new PhpParser\NodeDumper([
'dumpComments' => true,
'dumpPositions' => $attributes['with-positions'],
]);
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$traverser = new PhpParser\NodeTraverser();
$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver);
foreach ($files as $file) {
if (strpos($file, '<?php') === 0) {
$code = $file;
fwrite(STDERR, "====> Code $code\n");
} else {
if (!file_exists($file)) {
fwrite(STDERR, "File $file does not exist.\n");
exit(1);
}
$code = file_get_contents($file);
fwrite(STDERR, "====> File $file:\n");
}
if ($attributes['with-recovery']) {
$errorHandler = new PhpParser\ErrorHandler\Collecting;
$stmts = $parser->parse($code, $errorHandler);
foreach ($errorHandler->getErrors() as $error) {
$message = formatErrorMessage($error, $code, $attributes['with-column-info']);
fwrite(STDERR, $message . "\n");
}
if (null === $stmts) {
continue;
}
} else {
try {
$stmts = $parser->parse($code);
} catch (PhpParser\Error $error) {
$message = formatErrorMessage($error, $code, $attributes['with-column-info']);
fwrite(STDERR, $message . "\n");
exit(1);
}
}
foreach ($operations as $operation) {
if ('dump' === $operation) {
fwrite(STDERR, "==> Node dump:\n");
echo $dumper->dump($stmts, $code), "\n";
} elseif ('pretty-print' === $operation) {
fwrite(STDERR, "==> Pretty print:\n");
echo $prettyPrinter->prettyPrintFile($stmts), "\n";
} elseif ('json-dump' === $operation) {
fwrite(STDERR, "==> JSON dump:\n");
echo json_encode($stmts, JSON_PRETTY_PRINT), "\n";
} elseif ('var-dump' === $operation) {
fwrite(STDERR, "==> var_dump():\n");
var_dump($stmts);
} elseif ('resolve-names' === $operation) {
fwrite(STDERR, "==> Resolved names.\n");
$stmts = $traverser->traverse($stmts);
}
}
}
function formatErrorMessage(PhpParser\Error $e, $code, $withColumnInfo) {
if ($withColumnInfo && $e->hasColumnInfo()) {
return $e->getMessageWithColumnInfo($code);
} else {
return $e->getMessage();
}
}
function showHelp($error = '') {
if ($error) {
fwrite(STDERR, $error . "\n\n");
}
fwrite($error ? STDERR : STDOUT, <<<OUTPUT
Usage: php-parse [operations] file1.php [file2.php ...]
or: php-parse [operations] "<?php code"
Turn PHP source code into an abstract syntax tree.
Operations is a list of the following options (--dump by default):
-d, --dump Dump nodes using NodeDumper
-p, --pretty-print Pretty print file using PrettyPrinter\Standard
-j, --json-dump Print json_encode() result
--var-dump var_dump() nodes (for exact structure)
-N, --resolve-names Resolve names using NodeVisitor\NameResolver
-c, --with-column-info Show column-numbers for errors (if available)
-P, --with-positions Show positions in node dumps
-r, --with-recovery Use parsing with error recovery
-h, --help Display this page
Example:
php-parse -d -p -N -d file.php
Dumps nodes, pretty prints them, then resolves names and dumps them again.
OUTPUT
);
exit($error ? 1 : 0);
}
function parseArgs($args) {
$operations = [];
$files = [];
$attributes = [
'with-column-info' => false,
'with-positions' => false,
'with-recovery' => false,
];
array_shift($args);
$parseOptions = true;
foreach ($args as $arg) {
if (!$parseOptions) {
$files[] = $arg;
continue;
}
switch ($arg) {
case '--dump':
case '-d':
$operations[] = 'dump';
break;
case '--pretty-print':
case '-p':
$operations[] = 'pretty-print';
break;
case '--json-dump':
case '-j':
$operations[] = 'json-dump';
break;
case '--var-dump':
$operations[] = 'var-dump';
break;
case '--resolve-names':
case '-N';
$operations[] = 'resolve-names';
break;
case '--with-column-info':
case '-c';
$attributes['with-column-info'] = true;
break;
case '--with-positions':
case '-P':
$attributes['with-positions'] = true;
break;
case '--with-recovery':
case '-r':
$attributes['with-recovery'] = true;
break;
case '--help':
case '-h';
showHelp();
break;
case '--':
$parseOptions = false;
break;
default:
if ($arg[0] === '-') {
showHelp("Invalid operation $arg.");
} else {
$files[] = $arg;
}
}
}
return [$operations, $files, $attributes];
}

98
vendor/bin/phpunit vendored Executable file
View File

@@ -0,0 +1,98 @@
#!/usr/bin/env php
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (!version_compare(PHP_VERSION, PHP_VERSION, '=')) {
fwrite(
STDERR,
sprintf(
'%s declares an invalid value for PHP_VERSION.' . PHP_EOL .
'This breaks fundamental functionality such as version_compare().' . PHP_EOL .
'Please use a different PHP interpreter.' . PHP_EOL,
PHP_BINARY
)
);
die(1);
}
if (version_compare('7.3.0', PHP_VERSION, '>')) {
fwrite(
STDERR,
sprintf(
'This version of PHPUnit requires PHP >= 7.3.' . PHP_EOL .
'You are using PHP %s (%s).' . PHP_EOL,
PHP_VERSION,
PHP_BINARY
)
);
die(1);
}
foreach (['dom', 'json', 'libxml', 'mbstring', 'tokenizer', 'xml', 'xmlwriter'] as $extension) {
if (extension_loaded($extension)) {
continue;
}
fwrite(
STDERR,
sprintf(
'PHPUnit requires the "%s" extension.' . PHP_EOL,
$extension
)
);
die(1);
}
if (!ini_get('date.timezone')) {
ini_set('date.timezone', 'UTC');
}
if (isset($GLOBALS['_composer_autoload_path'])) {
define('PHPUNIT_COMPOSER_INSTALL', $GLOBALS['_composer_autoload_path']);
unset($GLOBALS['_composer_autoload_path']);
} else {
foreach (array(__DIR__ . '/../../autoload.php', __DIR__ . '/../vendor/autoload.php', __DIR__ . '/vendor/autoload.php') as $file) {
if (file_exists($file)) {
define('PHPUNIT_COMPOSER_INSTALL', $file);
break;
}
}
unset($file);
}
if (!defined('PHPUNIT_COMPOSER_INSTALL')) {
fwrite(
STDERR,
'You need to set up the project dependencies using Composer:' . PHP_EOL . PHP_EOL .
' composer install' . PHP_EOL . PHP_EOL .
'You can learn all about Composer on https://getcomposer.org/.' . PHP_EOL
);
die(1);
}
$options = getopt('', array('prepend:'));
if (isset($options['prepend'])) {
require $options['prepend'];
}
unset($options);
require PHPUNIT_COMPOSER_INSTALL;
PHPUnit\TextUI\Command::main();

BIN
vendor/bin/pint vendored Executable file

Binary file not shown.

148
vendor/bin/psysh vendored Executable file
View File

@@ -0,0 +1,148 @@
#!/usr/bin/env php
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2022 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
// Try to find an autoloader for a local psysh version.
// We'll wrap this whole mess in a Closure so it doesn't leak any globals.
call_user_func(function () {
$cwd = null;
// Find the cwd arg (if present)
$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : array();
foreach ($argv as $i => $arg) {
if ($arg === '--cwd') {
if ($i >= count($argv) - 1) {
fwrite(STDERR, 'Missing --cwd argument.' . PHP_EOL);
exit(1);
}
$cwd = $argv[$i + 1];
break;
}
if (preg_match('/^--cwd=/', $arg)) {
$cwd = substr($arg, 6);
break;
}
}
// Or fall back to the actual cwd
if (!isset($cwd)) {
$cwd = getcwd();
}
$cwd = str_replace('\\', '/', $cwd);
$chunks = explode('/', $cwd);
while (!empty($chunks)) {
$path = implode('/', $chunks);
$prettyPath = $path;
if (isset($_SERVER['HOME']) && $_SERVER['HOME']) {
$prettyPath = preg_replace('/^' . preg_quote($_SERVER['HOME'], '/') . '/', '~', $path);
}
// Find composer.json
if (is_file($path . '/composer.json')) {
if ($cfg = json_decode(file_get_contents($path . '/composer.json'), true)) {
if (isset($cfg['name']) && $cfg['name'] === 'psy/psysh') {
// We're inside the psysh project. Let's use the local Composer autoload.
if (is_file($path . '/vendor/autoload.php')) {
if (realpath($path) !== realpath(__DIR__ . '/..')) {
fwrite(STDERR, 'Using local PsySH version at ' . $prettyPath . PHP_EOL);
}
require $path . '/vendor/autoload.php';
}
return;
}
}
}
// Or a composer.lock
if (is_file($path . '/composer.lock')) {
if ($cfg = json_decode(file_get_contents($path . '/composer.lock'), true)) {
foreach (array_merge($cfg['packages'], $cfg['packages-dev']) as $pkg) {
if (isset($pkg['name']) && $pkg['name'] === 'psy/psysh') {
// We're inside a project which requires psysh. We'll use the local Composer autoload.
if (is_file($path . '/vendor/autoload.php')) {
if (realpath($path . '/vendor') !== realpath(__DIR__ . '/../../..')) {
fwrite(STDERR, 'Using local PsySH version at ' . $prettyPath . PHP_EOL);
}
require $path . '/vendor/autoload.php';
}
return;
}
}
}
}
array_pop($chunks);
}
});
// We didn't find an autoloader for a local version, so use the autoloader that
// came with this script.
if (!class_exists('Psy\Shell')) {
/* <<< */
if (is_file(__DIR__ . '/../vendor/autoload.php')) {
require __DIR__ . '/../vendor/autoload.php';
} elseif (is_file(__DIR__ . '/../../../autoload.php')) {
require __DIR__ . '/../../../autoload.php';
} else {
fwrite(STDERR, 'PsySH dependencies not found, be sure to run `composer install`.' . PHP_EOL);
fwrite(STDERR, 'See https://getcomposer.org to get Composer.' . PHP_EOL);
exit(1);
}
/* >>> */
}
// If the psysh binary was included directly, assume they just wanted an
// autoloader and bail early.
//
// Keep this PHP 5.3 and 5.4 code around for a while in case someone is using a
// globally installed psysh as a bin launcher for older local versions.
if (version_compare(PHP_VERSION, '5.3.6', '<')) {
$trace = debug_backtrace();
} elseif (version_compare(PHP_VERSION, '5.4.0', '<')) {
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
} else {
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
}
if (Psy\Shell::isIncluded($trace)) {
unset($trace);
return;
}
// Clean up after ourselves.
unset($trace);
// If the local version is too old, we can't do this
if (!function_exists('Psy\bin')) {
$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : array();
$first = array_shift($argv);
if (preg_match('/php(\.exe)?$/', $first)) {
array_shift($argv);
}
array_unshift($argv, 'vendor/bin/psysh');
fwrite(STDERR, 'A local PsySH dependency was found, but it cannot be loaded. Please update to' . PHP_EOL);
fwrite(STDERR, 'the latest version, or run the local copy directly, e.g.:' . PHP_EOL);
fwrite(STDERR, PHP_EOL);
fwrite(STDERR, ' ' . implode(' ', $argv) . PHP_EOL);
exit(1);
}
// And go!
call_user_func(Psy\bin());

486
vendor/bin/sail vendored Executable file
View File

@@ -0,0 +1,486 @@
#!/usr/bin/env bash
UNAMEOUT="$(uname -s)"
# Verify operating system is supported...
case "${UNAMEOUT}" in
Linux*) MACHINE=linux;;
Darwin*) MACHINE=mac;;
*) MACHINE="UNKNOWN"
esac
if [ "$MACHINE" == "UNKNOWN" ]; then
echo "Unsupported operating system [$(uname -s)]. Laravel Sail supports macOS, Linux, and Windows (WSL2)." >&2
exit 1
fi
# Determine if stdout is a terminal...
if test -t 1; then
# Determine if colors are supported...
ncolors=$(tput colors)
if test -n "$ncolors" && test "$ncolors" -ge 8; then
BOLD="$(tput bold)"
YELLOW="$(tput setaf 3)"
GREEN="$(tput setaf 2)"
NC="$(tput sgr0)"
fi
fi
# Function that prints the available commands...
function display_help {
echo "Laravel Sail"
echo
echo "${YELLOW}Usage:${NC}" >&2
echo " sail COMMAND [options] [arguments]"
echo
echo "Unknown commands are passed to the docker-compose binary."
echo
echo "${YELLOW}docker-compose Commands:${NC}"
echo " ${GREEN}sail up${NC} Start the application"
echo " ${GREEN}sail up -d${NC} Start the application in the background"
echo " ${GREEN}sail stop${NC} Stop the application"
echo " ${GREEN}sail restart${NC} Restart the application"
echo " ${GREEN}sail ps${NC} Display the status of all containers"
echo
echo "${YELLOW}Artisan Commands:${NC}"
echo " ${GREEN}sail artisan ...${NC} Run an Artisan command"
echo " ${GREEN}sail artisan queue:work${NC}"
echo
echo "${YELLOW}PHP Commands:${NC}"
echo " ${GREEN}sail php ...${NC} Run a snippet of PHP code"
echo " ${GREEN}sail php -v${NC}"
echo
echo "${YELLOW}Composer Commands:${NC}"
echo " ${GREEN}sail composer ...${NC} Run a Composer command"
echo " ${GREEN}sail composer require laravel/sanctum${NC}"
echo
echo "${YELLOW}Node Commands:${NC}"
echo " ${GREEN}sail node ...${NC} Run a Node command"
echo " ${GREEN}sail node --version${NC}"
echo
echo "${YELLOW}NPM Commands:${NC}"
echo " ${GREEN}sail npm ...${NC} Run a npm command"
echo " ${GREEN}sail npx${NC} Run a npx command"
echo " ${GREEN}sail npm run prod${NC}"
echo
echo "${YELLOW}Yarn Commands:${NC}"
echo " ${GREEN}sail yarn ...${NC} Run a Yarn command"
echo " ${GREEN}sail yarn run prod${NC}"
echo
echo "${YELLOW}Database Commands:${NC}"
echo " ${GREEN}sail mysql${NC} Start a MySQL CLI session within the 'mysql' container"
echo " ${GREEN}sail mariadb${NC} Start a MySQL CLI session within the 'mariadb' container"
echo " ${GREEN}sail psql${NC} Start a PostgreSQL CLI session within the 'pgsql' container"
echo " ${GREEN}sail redis${NC} Start a Redis CLI session within the 'redis' container"
echo
echo "${YELLOW}Debugging:${NC}"
echo " ${GREEN}sail debug ...${NC} Run an Artisan command in debug mode"
echo " ${GREEN}sail debug queue:work${NC}"
echo
echo "${YELLOW}Running Tests:${NC}"
echo " ${GREEN}sail test${NC} Run the PHPUnit tests via the Artisan test command"
echo " ${GREEN}sail phpunit ...${NC} Run PHPUnit"
echo " ${GREEN}sail pint ...${NC} Run Pint"
echo " ${GREEN}sail dusk${NC} Run the Dusk tests (Requires the laravel/dusk package)"
echo " ${GREEN}sail dusk:fails${NC} Re-run previously failed Dusk tests (Requires the laravel/dusk package)"
echo
echo "${YELLOW}Container CLI:${NC}"
echo " ${GREEN}sail shell${NC} Start a shell session within the application container"
echo " ${GREEN}sail bash${NC} Alias for 'sail shell'"
echo " ${GREEN}sail root-shell${NC} Start a root shell session within the application container"
echo " ${GREEN}sail root-bash${NC} Alias for 'sail root-shell'"
echo " ${GREEN}sail tinker${NC} Start a new Laravel Tinker session"
echo
echo "${YELLOW}Sharing:${NC}"
echo " ${GREEN}sail share${NC} Share the application publicly via a temporary URL"
echo
echo "${YELLOW}Binaries:${NC}"
echo " ${GREEN}sail bin ...${NC} Run Composer binary scripts from the vendor/bin directory"
echo
echo "${YELLOW}Customization:${NC}"
echo " ${GREEN}sail artisan sail:publish${NC} Publish the Sail configuration files"
echo " ${GREEN}sail build --no-cache${NC} Rebuild all of the Sail containers"
exit 1
}
# Proxy the "help" command...
if [ $# -gt 0 ]; then
if [ "$1" == "help" ] || [ "$1" == "-h" ] || [ "$1" == "-help" ] || [ "$1" == "--help" ]; then
display_help
fi
else
display_help
fi
# Source the ".env" file so Laravel's environment variables are available...
if [ -f ./.env ]; then
source ./.env
fi
# Define environment variables...
export APP_PORT=${APP_PORT:-80}
export APP_SERVICE=${APP_SERVICE:-"laravel.test"}
export DB_PORT=${DB_PORT:-3306}
export WWWUSER=${WWWUSER:-$UID}
export WWWGROUP=${WWWGROUP:-$(id -g)}
export SAIL_FILES=${SAIL_FILES:-""}
export SAIL_SHARE_DASHBOARD=${SAIL_SHARE_DASHBOARD:-4040}
export SAIL_SHARE_SERVER_HOST=${SAIL_SHARE_SERVER_HOST:-"laravel-sail.site"}
export SAIL_SHARE_SERVER_PORT=${SAIL_SHARE_SERVER_PORT:-8080}
export SAIL_SHARE_SUBDOMAIN=${SAIL_SHARE_SUBDOMAIN:-""}
# Function that outputs Sail is not running...
function sail_is_not_running {
echo "${BOLD}Sail is not running.${NC}" >&2
echo "" >&2
echo "${BOLD}You may Sail using the following commands:${NC} './vendor/bin/sail up' or './vendor/bin/sail up -d'" >&2
exit 1
}
# Define Docker Compose command prefix...
docker compose &> /dev/null
if [ $? == 0 ]; then
DOCKER_COMPOSE=(docker compose)
else
DOCKER_COMPOSE=(docker-compose)
fi
if [ -n "$SAIL_FILES" ]; then
# Convert SAIL_FILES to an array...
IFS=':' read -ra SAIL_FILES <<< "$SAIL_FILES"
for FILE in "${SAIL_FILES[@]}"; do
if [ -f "$FILE" ]; then
DOCKER_COMPOSE+=(-f "$FILE")
else
echo "${BOLD}Unable to find Docker Compose file: '${FILE}'${NC}" >&2
exit 1
fi
done
fi
EXEC="yes"
if [ -z "$SAIL_SKIP_CHECKS" ]; then
# Ensure that Docker is running...
if ! docker info > /dev/null 2>&1; then
echo "${BOLD}Docker is not running.${NC}" >&2
exit 1
fi
# Determine if Sail is currently up...
if "${DOCKER_COMPOSE[@]}" ps "$APP_SERVICE" 2>&1 | grep 'Exit\|exited'; then
echo "${BOLD}Shutting down old Sail processes...${NC}" >&2
"${DOCKER_COMPOSE[@]}" down > /dev/null 2>&1
EXEC="no"
elif [ -z "$("${DOCKER_COMPOSE[@]}" ps -q)" ]; then
EXEC="no"
fi
fi
ARGS=()
# Proxy PHP commands to the "php" binary on the application container...
if [ "$1" == "php" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" "php" "$@")
else
sail_is_not_running
fi
# Proxy vendor binary commands on the application container...
elif [ "$1" == "bin" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" ./vendor/bin/"$@")
else
sail_is_not_running
fi
# Proxy docker-compose commands to the docker-compose binary on the application container...
elif [ "$1" == "docker-compose" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" "${DOCKER_COMPOSE[@]}")
else
sail_is_not_running
fi
# Proxy Composer commands to the "composer" binary on the application container...
elif [ "$1" == "composer" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" "composer" "$@")
else
sail_is_not_running
fi
# Proxy Artisan commands to the "artisan" binary on the application container...
elif [ "$1" == "artisan" ] || [ "$1" == "art" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" php artisan "$@")
else
sail_is_not_running
fi
# Proxy the "debug" command to the "php artisan" binary on the application container with xdebug enabled...
elif [ "$1" == "debug" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail -e XDEBUG_SESSION=1)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" php artisan "$@")
else
sail_is_not_running
fi
# Proxy the "test" command to the "php artisan test" Artisan command...
elif [ "$1" == "test" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" php artisan test "$@")
else
sail_is_not_running
fi
# Proxy the "phpunit" command to "php vendor/bin/phpunit"...
elif [ "$1" == "phpunit" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" php vendor/bin/phpunit "$@")
else
sail_is_not_running
fi
# Proxy the "pint" command to "php vendor/bin/pint"...
elif [ "$1" == "pint" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" php vendor/bin/pint "$@")
else
sail_is_not_running
fi
# Proxy the "dusk" command to the "php artisan dusk" Artisan command...
elif [ "$1" == "dusk" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=(-e "APP_URL=http://${APP_SERVICE}")
ARGS+=(-e "DUSK_DRIVER_URL=http://selenium:4444/wd/hub")
ARGS+=("$APP_SERVICE" php artisan dusk "$@")
else
sail_is_not_running
fi
# Proxy the "dusk:fails" command to the "php artisan dusk:fails" Artisan command...
elif [ "$1" == "dusk:fails" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=(-e "APP_URL=http://${APP_SERVICE}")
ARGS+=(-e "DUSK_DRIVER_URL=http://selenium:4444/wd/hub")
ARGS+=("$APP_SERVICE" php artisan dusk:fails "$@")
else
sail_is_not_running
fi
# Initiate a Laravel Tinker session within the application container...
elif [ "$1" == "tinker" ] ; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" php artisan tinker)
else
sail_is_not_running
fi
# Proxy Node commands to the "node" binary on the application container...
elif [ "$1" == "node" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" node "$@")
else
sail_is_not_running
fi
# Proxy NPM commands to the "npm" binary on the application container...
elif [ "$1" == "npm" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" npm "$@")
else
sail_is_not_running
fi
# Proxy NPX commands to the "npx" binary on the application container...
elif [ "$1" == "npx" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" npx "$@")
else
sail_is_not_running
fi
# Proxy YARN commands to the "yarn" binary on the application container...
elif [ "$1" == "yarn" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" yarn "$@")
else
sail_is_not_running
fi
# Initiate a MySQL CLI terminal session within the "mysql" container...
elif [ "$1" == "mysql" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=(mysql bash -c)
ARGS+=("MYSQL_PWD=\${MYSQL_PASSWORD} mysql -u \${MYSQL_USER} \${MYSQL_DATABASE}")
else
sail_is_not_running
fi
# Initiate a MySQL CLI terminal session within the "mariadb" container...
elif [ "$1" == "mariadb" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=(mariadb bash -c)
ARGS+=("MYSQL_PWD=\${MYSQL_PASSWORD} mysql -u \${MYSQL_USER} \${MYSQL_DATABASE}")
else
sail_is_not_running
fi
# Initiate a PostgreSQL CLI terminal session within the "pgsql" container...
elif [ "$1" == "psql" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=(pgsql bash -c)
ARGS+=("PGPASSWORD=\${PGPASSWORD} psql -U \${POSTGRES_USER} \${POSTGRES_DB}")
else
sail_is_not_running
fi
# Initiate a Bash shell within the application container...
elif [ "$1" == "shell" ] || [ "$1" == "bash" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec -u sail)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" bash "$@")
else
sail_is_not_running
fi
# Initiate a root user Bash shell within the application container...
elif [ "$1" == "root-shell" ] || [ "$1" == "root-bash" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=("$APP_SERVICE" bash "$@")
else
sail_is_not_running
fi
# Initiate a Redis CLI terminal session within the "redis" container...
elif [ "$1" == "redis" ] ; then
shift 1
if [ "$EXEC" == "yes" ]; then
ARGS+=(exec)
[ ! -t 0 ] && ARGS+=(-T)
ARGS+=(redis redis-cli)
else
sail_is_not_running
fi
# Share the site...
elif [ "$1" == "share" ]; then
shift 1
if [ "$EXEC" == "yes" ]; then
docker run --init --rm -p "$SAIL_SHARE_DASHBOARD":4040 -t beyondcodegmbh/expose-server:latest share http://host.docker.internal:"$APP_PORT" \
--server-host="$SAIL_SHARE_SERVER_HOST" \
--server-port="$SAIL_SHARE_SERVER_PORT" \
--auth="$SAIL_SHARE_TOKEN" \
--subdomain="$SAIL_SHARE_SUBDOMAIN" \
"$@"
exit
else
sail_is_not_running
fi
# Pass unknown commands to the "docker-compose" binary...
else
ARGS+=("$@")
fi
# Run Docker Compose with the defined arguments...
"${DOCKER_COMPOSE[@]}" "${ARGS[@]}"

63
vendor/bin/var-dump-server vendored Executable file
View File

@@ -0,0 +1,63 @@
#!/usr/bin/env php
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Starts a dump server to collect and output dumps on a single place with multiple formats support.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\VarDumper\Command\ServerDumpCommand;
use Symfony\Component\VarDumper\Server\DumpServer;
function includeIfExists(string $file): bool
{
return file_exists($file) && include $file;
}
if (
!includeIfExists(__DIR__ . '/../../../../autoload.php') &&
!includeIfExists(__DIR__ . '/../../vendor/autoload.php') &&
!includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php')
) {
fwrite(STDERR, 'Install dependencies using Composer.'.PHP_EOL);
exit(1);
}
if (!class_exists(Application::class)) {
fwrite(STDERR, 'You need the "symfony/console" component in order to run the VarDumper server.'.PHP_EOL);
exit(1);
}
$input = new ArgvInput();
$output = new ConsoleOutput();
$defaultHost = '127.0.0.1:9912';
$host = $input->getParameterOption(['--host'], $_SERVER['VAR_DUMPER_SERVER'] ?? $defaultHost, true);
$logger = interface_exists(LoggerInterface::class) ? new ConsoleLogger($output->getErrorOutput()) : null;
$app = new Application();
$app->getDefinition()->addOption(
new InputOption('--host', null, InputOption::VALUE_REQUIRED, 'The address the server should listen to', $defaultHost)
);
$app->add($command = new ServerDumpCommand(new DumpServer($host, $logger)))
->getApplication()
->setDefaultCommand($command->getName(), true)
->run($input, $output)
;

433
vendor/brick/math/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,433 @@
# Changelog
All notable changes to this project will be documented in this file.
## [0.10.2](https://github.com/brick/math/releases/tag/0.10.2) - 2022-08-11
👌 **Improvements**
- `BigRational::toFloat()` now simplifies the fraction before performing division (#73) thanks to @olsavmic
## [0.10.1](https://github.com/brick/math/releases/tag/0.10.1) - 2022-08-02
**New features**
- `BigInteger::gcdMultiple()` returns the GCD of multiple `BigInteger` numbers
## [0.10.0](https://github.com/brick/math/releases/tag/0.10.0) - 2022-06-18
💥 **Breaking changes**
- Minimum PHP version is now 7.4
## [0.9.3](https://github.com/brick/math/releases/tag/0.9.3) - 2021-08-15
🚀 **Compatibility with PHP 8.1**
- Support for custom object serialization; this removes a warning on PHP 8.1 due to the `Serializable` interface being deprecated (#60) thanks @TRowbotham
## [0.9.2](https://github.com/brick/math/releases/tag/0.9.2) - 2021-01-20
🐛 **Bug fix**
- Incorrect results could be returned when using the BCMath calculator, with a default scale set with `bcscale()`, on PHP >= 7.2 (#55).
## [0.9.1](https://github.com/brick/math/releases/tag/0.9.1) - 2020-08-19
**New features**
- `BigInteger::not()` returns the bitwise `NOT` value
🐛 **Bug fixes**
- `BigInteger::toBytes()` could return an incorrect binary representation for some numbers
- The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available
## [0.9.0](https://github.com/brick/math/releases/tag/0.9.0) - 2020-08-18
👌 **Improvements**
- `BigNumber::of()` now accepts `.123` and `123.` formats, both of which return a `BigDecimal`
💥 **Breaking changes**
- Deprecated method `BigInteger::powerMod()` has been removed - use `modPow()` instead
- Deprecated method `BigInteger::parse()` has been removed - use `fromBase()` instead
## [0.8.17](https://github.com/brick/math/releases/tag/0.8.17) - 2020-08-19
🐛 **Bug fix**
- `BigInteger::toBytes()` could return an incorrect binary representation for some numbers
- The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available
## [0.8.16](https://github.com/brick/math/releases/tag/0.8.16) - 2020-08-18
🚑 **Critical fix**
- This version reintroduces the deprecated `BigInteger::parse()` method, that has been removed by mistake in version `0.8.9` and should have lasted for the whole `0.8` release cycle.
**New features**
- `BigInteger::modInverse()` calculates a modular multiplicative inverse
- `BigInteger::fromBytes()` creates a `BigInteger` from a byte string
- `BigInteger::toBytes()` converts a `BigInteger` to a byte string
- `BigInteger::randomBits()` creates a pseudo-random `BigInteger` of a given bit length
- `BigInteger::randomRange()` creates a pseudo-random `BigInteger` between two bounds
💩 **Deprecations**
- `BigInteger::powerMod()` is now deprecated in favour of `modPow()`
## [0.8.15](https://github.com/brick/math/releases/tag/0.8.15) - 2020-04-15
🐛 **Fixes**
- added missing `ext-json` requirement, due to `BigNumber` implementing `JsonSerializable`
⚡️ **Optimizations**
- additional optimization in `BigInteger::remainder()`
## [0.8.14](https://github.com/brick/math/releases/tag/0.8.14) - 2020-02-18
**New features**
- `BigInteger::getLowestSetBit()` returns the index of the rightmost one bit
## [0.8.13](https://github.com/brick/math/releases/tag/0.8.13) - 2020-02-16
**New features**
- `BigInteger::isEven()` tests whether the number is even
- `BigInteger::isOdd()` tests whether the number is odd
- `BigInteger::testBit()` tests if a bit is set
- `BigInteger::getBitLength()` returns the number of bits in the minimal representation of the number
## [0.8.12](https://github.com/brick/math/releases/tag/0.8.12) - 2020-02-03
🛠️ **Maintenance release**
Classes are now annotated for better static analysis with [psalm](https://psalm.dev/).
This is a maintenance release: no bug fixes, no new features, no breaking changes.
## [0.8.11](https://github.com/brick/math/releases/tag/0.8.11) - 2020-01-23
**New feature**
`BigInteger::powerMod()` performs a power-with-modulo operation. Useful for crypto.
## [0.8.10](https://github.com/brick/math/releases/tag/0.8.10) - 2020-01-21
**New feature**
`BigInteger::mod()` returns the **modulo** of two numbers. The *modulo* differs from the *remainder* when the signs of the operands are different.
## [0.8.9](https://github.com/brick/math/releases/tag/0.8.9) - 2020-01-08
⚡️ **Performance improvements**
A few additional optimizations in `BigInteger` and `BigDecimal` when one of the operands can be returned as is. Thanks to @tomtomsen in #24.
## [0.8.8](https://github.com/brick/math/releases/tag/0.8.8) - 2019-04-25
🐛 **Bug fixes**
- `BigInteger::toBase()` could return an empty string for zero values (BCMath & Native calculators only, GMP calculator unaffected)
**New features**
- `BigInteger::toArbitraryBase()` converts a number to an arbitrary base, using a custom alphabet
- `BigInteger::fromArbitraryBase()` converts a string in an arbitrary base, using a custom alphabet, back to a number
These methods can be used as the foundation to convert strings between different bases/alphabets, using BigInteger as an intermediate representation.
💩 **Deprecations**
- `BigInteger::parse()` is now deprecated in favour of `fromBase()`
`BigInteger::fromBase()` works the same way as `parse()`, with 2 minor differences:
- the `$base` parameter is required, it does not default to `10`
- it throws a `NumberFormatException` instead of an `InvalidArgumentException` when the number is malformed
## [0.8.7](https://github.com/brick/math/releases/tag/0.8.7) - 2019-04-20
**Improvements**
- Safer conversion from `float` when using custom locales
- **Much faster** `NativeCalculator` implementation 🚀
You can expect **at least a 3x performance improvement** for common arithmetic operations when using the library on systems without GMP or BCMath; it gets exponentially faster on multiplications with a high number of digits. This is due to calculations now being performed on whole blocks of digits (the block size depending on the platform, 32-bit or 64-bit) instead of digit-by-digit as before.
## [0.8.6](https://github.com/brick/math/releases/tag/0.8.6) - 2019-04-11
**New method**
`BigNumber::sum()` returns the sum of one or more numbers.
## [0.8.5](https://github.com/brick/math/releases/tag/0.8.5) - 2019-02-12
**Bug fix**: `of()` factory methods could fail when passing a `float` in environments using a `LC_NUMERIC` locale with a decimal separator other than `'.'` (#20).
Thanks @manowark 👍
## [0.8.4](https://github.com/brick/math/releases/tag/0.8.4) - 2018-12-07
**New method**
`BigDecimal::sqrt()` calculates the square root of a decimal number, to a given scale.
## [0.8.3](https://github.com/brick/math/releases/tag/0.8.3) - 2018-12-06
**New method**
`BigInteger::sqrt()` calculates the square root of a number (thanks @peter279k).
**New exception**
`NegativeNumberException` is thrown when calling `sqrt()` on a negative number.
## [0.8.2](https://github.com/brick/math/releases/tag/0.8.2) - 2018-11-08
**Performance update**
- Further improvement of `toInt()` performance
- `NativeCalculator` can now perform some multiplications more efficiently
## [0.8.1](https://github.com/brick/math/releases/tag/0.8.1) - 2018-11-07
Performance optimization of `toInt()` methods.
## [0.8.0](https://github.com/brick/math/releases/tag/0.8.0) - 2018-10-13
**Breaking changes**
The following deprecated methods have been removed. Use the new method name instead:
| Method removed | Replacement method |
| --- | --- |
| `BigDecimal::getIntegral()` | `BigDecimal::getIntegralPart()` |
| `BigDecimal::getFraction()` | `BigDecimal::getFractionalPart()` |
---
**New features**
`BigInteger` has been augmented with 5 new methods for bitwise operations:
| New method | Description |
| --- | --- |
| `and()` | performs a bitwise `AND` operation on two numbers |
| `or()` | performs a bitwise `OR` operation on two numbers |
| `xor()` | performs a bitwise `XOR` operation on two numbers |
| `shiftedLeft()` | returns the number shifted left by a number of bits |
| `shiftedRight()` | returns the number shifted right by a number of bits |
Thanks to @DASPRiD 👍
## [0.7.3](https://github.com/brick/math/releases/tag/0.7.3) - 2018-08-20
**New method:** `BigDecimal::hasNonZeroFractionalPart()`
**Renamed/deprecated methods:**
- `BigDecimal::getIntegral()` has been renamed to `getIntegralPart()` and is now deprecated
- `BigDecimal::getFraction()` has been renamed to `getFractionalPart()` and is now deprecated
## [0.7.2](https://github.com/brick/math/releases/tag/0.7.2) - 2018-07-21
**Performance update**
`BigInteger::parse()` and `toBase()` now use GMP's built-in base conversion features when available.
## [0.7.1](https://github.com/brick/math/releases/tag/0.7.1) - 2018-03-01
This is a maintenance release, no code has been changed.
- When installed with `--no-dev`, the autoloader does not autoload tests anymore
- Tests and other files unnecessary for production are excluded from the dist package
This will help make installations more compact.
## [0.7.0](https://github.com/brick/math/releases/tag/0.7.0) - 2017-10-02
Methods renamed:
- `BigNumber:sign()` has been renamed to `getSign()`
- `BigDecimal::unscaledValue()` has been renamed to `getUnscaledValue()`
- `BigDecimal::scale()` has been renamed to `getScale()`
- `BigDecimal::integral()` has been renamed to `getIntegral()`
- `BigDecimal::fraction()` has been renamed to `getFraction()`
- `BigRational::numerator()` has been renamed to `getNumerator()`
- `BigRational::denominator()` has been renamed to `getDenominator()`
Classes renamed:
- `ArithmeticException` has been renamed to `MathException`
## [0.6.2](https://github.com/brick/math/releases/tag/0.6.2) - 2017-10-02
The base class for all exceptions is now `MathException`.
`ArithmeticException` has been deprecated, and will be removed in 0.7.0.
## [0.6.1](https://github.com/brick/math/releases/tag/0.6.1) - 2017-10-02
A number of methods have been renamed:
- `BigNumber:sign()` is deprecated; use `getSign()` instead
- `BigDecimal::unscaledValue()` is deprecated; use `getUnscaledValue()` instead
- `BigDecimal::scale()` is deprecated; use `getScale()` instead
- `BigDecimal::integral()` is deprecated; use `getIntegral()` instead
- `BigDecimal::fraction()` is deprecated; use `getFraction()` instead
- `BigRational::numerator()` is deprecated; use `getNumerator()` instead
- `BigRational::denominator()` is deprecated; use `getDenominator()` instead
The old methods will be removed in version 0.7.0.
## [0.6.0](https://github.com/brick/math/releases/tag/0.6.0) - 2017-08-25
- Minimum PHP version is now [7.1](https://gophp71.org/); for PHP 5.6 and PHP 7.0 support, use version `0.5`
- Deprecated method `BigDecimal::withScale()` has been removed; use `toScale()` instead
- Method `BigNumber::toInteger()` has been renamed to `toInt()`
## [0.5.4](https://github.com/brick/math/releases/tag/0.5.4) - 2016-10-17
`BigNumber` classes now implement [JsonSerializable](http://php.net/manual/en/class.jsonserializable.php).
The JSON output is always a string.
## [0.5.3](https://github.com/brick/math/releases/tag/0.5.3) - 2016-03-31
This is a bugfix release. Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
## [0.5.2](https://github.com/brick/math/releases/tag/0.5.2) - 2015-08-06
The `$scale` parameter of `BigDecimal::dividedBy()` is now optional again.
## [0.5.1](https://github.com/brick/math/releases/tag/0.5.1) - 2015-07-05
**New method: `BigNumber::toScale()`**
This allows to convert any `BigNumber` to a `BigDecimal` with a given scale, using rounding if necessary.
## [0.5.0](https://github.com/brick/math/releases/tag/0.5.0) - 2015-07-04
**New features**
- Common `BigNumber` interface for all classes, with the following methods:
- `sign()` and derived methods (`isZero()`, `isPositive()`, ...)
- `compareTo()` and derived methods (`isEqualTo()`, `isGreaterThan()`, ...) that work across different `BigNumber` types
- `toBigInteger()`, `toBigDecimal()`, `toBigRational`() conversion methods
- `toInteger()` and `toFloat()` conversion methods to native types
- Unified `of()` behaviour: every class now accepts any type of number, provided that it can be safely converted to the current type
- New method: `BigDecimal::exactlyDividedBy()`; this method automatically computes the scale of the result, provided that the division yields a finite number of digits
- New methods: `BigRational::quotient()` and `remainder()`
- Fine-grained exceptions: `DivisionByZeroException`, `RoundingNecessaryException`, `NumberFormatException`
- Factory methods `zero()`, `one()` and `ten()` available in all classes
- Rounding mode reintroduced in `BigInteger::dividedBy()`
This release also comes with many performance improvements.
---
**Breaking changes**
- `BigInteger`:
- `getSign()` is renamed to `sign()`
- `toString()` is renamed to `toBase()`
- `BigInteger::dividedBy()` now throws an exception by default if the remainder is not zero; use `quotient()` to get the previous behaviour
- `BigDecimal`:
- `getSign()` is renamed to `sign()`
- `getUnscaledValue()` is renamed to `unscaledValue()`
- `getScale()` is renamed to `scale()`
- `getIntegral()` is renamed to `integral()`
- `getFraction()` is renamed to `fraction()`
- `divideAndRemainder()` is renamed to `quotientAndRemainder()`
- `dividedBy()` now takes a **mandatory** `$scale` parameter **before** the rounding mode
- `toBigInteger()` does not accept a `$roundingMode` parameter any more
- `toBigRational()` does not simplify the fraction any more; explicitly add `->simplified()` to get the previous behaviour
- `BigRational`:
- `getSign()` is renamed to `sign()`
- `getNumerator()` is renamed to `numerator()`
- `getDenominator()` is renamed to `denominator()`
- `of()` is renamed to `nd()`, while `parse()` is renamed to `of()`
- Miscellaneous:
- `ArithmeticException` is moved to an `Exception\` sub-namespace
- `of()` factory methods now throw `NumberFormatException` instead of `InvalidArgumentException`
## [0.4.3](https://github.com/brick/math/releases/tag/0.4.3) - 2016-03-31
Backport of two bug fixes from the 0.5 branch:
- `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected
- Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
## [0.4.2](https://github.com/brick/math/releases/tag/0.4.2) - 2015-06-16
New method: `BigDecimal::stripTrailingZeros()`
## [0.4.1](https://github.com/brick/math/releases/tag/0.4.1) - 2015-06-12
Introducing a `BigRational` class, to perform calculations on fractions of any size.
## [0.4.0](https://github.com/brick/math/releases/tag/0.4.0) - 2015-06-12
Rounding modes have been removed from `BigInteger`, and are now a concept specific to `BigDecimal`.
`BigInteger::dividedBy()` now always returns the quotient of the division.
## [0.3.5](https://github.com/brick/math/releases/tag/0.3.5) - 2016-03-31
Backport of two bug fixes from the 0.5 branch:
- `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected
- Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
## [0.3.4](https://github.com/brick/math/releases/tag/0.3.4) - 2015-06-11
New methods:
- `BigInteger::remainder()` returns the remainder of a division only
- `BigInteger::gcd()` returns the greatest common divisor of two numbers
## [0.3.3](https://github.com/brick/math/releases/tag/0.3.3) - 2015-06-07
Fix `toString()` not handling negative numbers.
## [0.3.2](https://github.com/brick/math/releases/tag/0.3.2) - 2015-06-07
`BigInteger` and `BigDecimal` now have a `getSign()` method that returns:
- `-1` if the number is negative
- `0` if the number is zero
- `1` if the number is positive
## [0.3.1](https://github.com/brick/math/releases/tag/0.3.1) - 2015-06-05
Minor performance improvements
## [0.3.0](https://github.com/brick/math/releases/tag/0.3.0) - 2015-06-04
The `$roundingMode` and `$scale` parameters have been swapped in `BigDecimal::dividedBy()`.
## [0.2.2](https://github.com/brick/math/releases/tag/0.2.2) - 2015-06-04
Stronger immutability guarantee for `BigInteger` and `BigDecimal`.
So far, it would have been possible to break immutability of these classes by calling the `unserialize()` internal function. This release fixes that.
## [0.2.1](https://github.com/brick/math/releases/tag/0.2.1) - 2015-06-02
Added `BigDecimal::divideAndRemainder()`
## [0.2.0](https://github.com/brick/math/releases/tag/0.2.0) - 2015-05-22
- `min()` and `max()` do not accept an `array` any more, but a variable number of parameters
- **minimum PHP version is now 5.6**
- continuous integration with PHP 7
## [0.1.1](https://github.com/brick/math/releases/tag/0.1.1) - 2014-09-01
- Added `BigInteger::power()`
- Added HHVM support
## [0.1.0](https://github.com/brick/math/releases/tag/0.1.0) - 2014-08-31
First beta release.

20
vendor/brick/math/LICENSE vendored Normal file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013-present Benjamin Morel
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.

35
vendor/brick/math/composer.json vendored Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "brick/math",
"description": "Arbitrary-precision arithmetic library",
"type": "library",
"keywords": [
"Brick",
"Math",
"Arbitrary-precision",
"Arithmetic",
"BigInteger",
"BigDecimal",
"BigRational",
"Bignum"
],
"license": "MIT",
"require": {
"php": "^7.4 || ^8.0",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
"php-coveralls/php-coveralls": "^2.2",
"vimeo/psalm": "4.25.0"
},
"autoload": {
"psr-4": {
"Brick\\Math\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Brick\\Math\\Tests\\": "tests/"
}
}
}

891
vendor/brick/math/src/BigDecimal.php vendored Normal file
View File

@@ -0,0 +1,891 @@
<?php
declare(strict_types=1);
namespace Brick\Math;
use Brick\Math\Exception\DivisionByZeroException;
use Brick\Math\Exception\MathException;
use Brick\Math\Exception\NegativeNumberException;
use Brick\Math\Internal\Calculator;
/**
* Immutable, arbitrary-precision signed decimal numbers.
*
* @psalm-immutable
*/
final class BigDecimal extends BigNumber
{
/**
* The unscaled value of this decimal number.
*
* This is a string of digits with an optional leading minus sign.
* No leading zero must be present.
* No leading minus sign must be present if the value is 0.
*/
private string $value;
/**
* The scale (number of digits after the decimal point) of this decimal number.
*
* This must be zero or more.
*/
private int $scale;
/**
* Protected constructor. Use a factory method to obtain an instance.
*
* @param string $value The unscaled value, validated.
* @param int $scale The scale, validated.
*/
protected function __construct(string $value, int $scale = 0)
{
$this->value = $value;
$this->scale = $scale;
}
/**
* Creates a BigDecimal of the given value.
*
* @param BigNumber|int|float|string $value
*
* @return BigDecimal
*
* @throws MathException If the value cannot be converted to a BigDecimal.
*
* @psalm-pure
*/
public static function of($value) : BigNumber
{
return parent::of($value)->toBigDecimal();
}
/**
* Creates a BigDecimal from an unscaled value and a scale.
*
* Example: `(12345, 3)` will result in the BigDecimal `12.345`.
*
* @param BigNumber|int|float|string $value The unscaled value. Must be convertible to a BigInteger.
* @param int $scale The scale of the number, positive or zero.
*
* @return BigDecimal
*
* @throws \InvalidArgumentException If the scale is negative.
*
* @psalm-pure
*/
public static function ofUnscaledValue($value, int $scale = 0) : BigDecimal
{
if ($scale < 0) {
throw new \InvalidArgumentException('The scale cannot be negative.');
}
return new BigDecimal((string) BigInteger::of($value), $scale);
}
/**
* Returns a BigDecimal representing zero, with a scale of zero.
*
* @return BigDecimal
*
* @psalm-pure
*/
public static function zero() : BigDecimal
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigDecimal|null $zero
*/
static $zero;
if ($zero === null) {
$zero = new BigDecimal('0');
}
return $zero;
}
/**
* Returns a BigDecimal representing one, with a scale of zero.
*
* @return BigDecimal
*
* @psalm-pure
*/
public static function one() : BigDecimal
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigDecimal|null $one
*/
static $one;
if ($one === null) {
$one = new BigDecimal('1');
}
return $one;
}
/**
* Returns a BigDecimal representing ten, with a scale of zero.
*
* @return BigDecimal
*
* @psalm-pure
*/
public static function ten() : BigDecimal
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigDecimal|null $ten
*/
static $ten;
if ($ten === null) {
$ten = new BigDecimal('10');
}
return $ten;
}
/**
* Returns the sum of this number and the given one.
*
* The result has a scale of `max($this->scale, $that->scale)`.
*
* @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal.
*
* @return BigDecimal The result.
*
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
*/
public function plus($that) : BigDecimal
{
$that = BigDecimal::of($that);
if ($that->value === '0' && $that->scale <= $this->scale) {
return $this;
}
if ($this->value === '0' && $this->scale <= $that->scale) {
return $that;
}
[$a, $b] = $this->scaleValues($this, $that);
$value = Calculator::get()->add($a, $b);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
return new BigDecimal($value, $scale);
}
/**
* Returns the difference of this number and the given one.
*
* The result has a scale of `max($this->scale, $that->scale)`.
*
* @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal.
*
* @return BigDecimal The result.
*
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
*/
public function minus($that) : BigDecimal
{
$that = BigDecimal::of($that);
if ($that->value === '0' && $that->scale <= $this->scale) {
return $this;
}
[$a, $b] = $this->scaleValues($this, $that);
$value = Calculator::get()->sub($a, $b);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
return new BigDecimal($value, $scale);
}
/**
* Returns the product of this number and the given one.
*
* The result has a scale of `$this->scale + $that->scale`.
*
* @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal.
*
* @return BigDecimal The result.
*
* @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal.
*/
public function multipliedBy($that) : BigDecimal
{
$that = BigDecimal::of($that);
if ($that->value === '1' && $that->scale === 0) {
return $this;
}
if ($this->value === '1' && $this->scale === 0) {
return $that;
}
$value = Calculator::get()->mul($this->value, $that->value);
$scale = $this->scale + $that->scale;
return new BigDecimal($value, $scale);
}
/**
* Returns the result of the division of this number by the given one, at the given scale.
*
* @param BigNumber|int|float|string $that The divisor.
* @param int|null $scale The desired scale, or null to use the scale of this number.
* @param int $roundingMode An optional rounding mode.
*
* @return BigDecimal
*
* @throws \InvalidArgumentException If the scale or rounding mode is invalid.
* @throws MathException If the number is invalid, is zero, or rounding was necessary.
*/
public function dividedBy($that, ?int $scale = null, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
{
$that = BigDecimal::of($that);
if ($that->isZero()) {
throw DivisionByZeroException::divisionByZero();
}
if ($scale === null) {
$scale = $this->scale;
} elseif ($scale < 0) {
throw new \InvalidArgumentException('Scale cannot be negative.');
}
if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) {
return $this;
}
$p = $this->valueWithMinScale($that->scale + $scale);
$q = $that->valueWithMinScale($this->scale - $scale);
$result = Calculator::get()->divRound($p, $q, $roundingMode);
return new BigDecimal($result, $scale);
}
/**
* Returns the exact result of the division of this number by the given one.
*
* The scale of the result is automatically calculated to fit all the fraction digits.
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @return BigDecimal The result.
*
* @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero,
* or the result yields an infinite number of digits.
*/
public function exactlyDividedBy($that) : BigDecimal
{
$that = BigDecimal::of($that);
if ($that->value === '0') {
throw DivisionByZeroException::divisionByZero();
}
[, $b] = $this->scaleValues($this, $that);
$d = \rtrim($b, '0');
$scale = \strlen($b) - \strlen($d);
$calculator = Calculator::get();
foreach ([5, 2] as $prime) {
for (;;) {
$lastDigit = (int) $d[-1];
if ($lastDigit % $prime !== 0) {
break;
}
$d = $calculator->divQ($d, (string) $prime);
$scale++;
}
}
return $this->dividedBy($that, $scale)->stripTrailingZeros();
}
/**
* Returns this number exponentiated to the given value.
*
* The result has a scale of `$this->scale * $exponent`.
*
* @param int $exponent The exponent.
*
* @return BigDecimal The result.
*
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
*/
public function power(int $exponent) : BigDecimal
{
if ($exponent === 0) {
return BigDecimal::one();
}
if ($exponent === 1) {
return $this;
}
if ($exponent < 0 || $exponent > Calculator::MAX_POWER) {
throw new \InvalidArgumentException(\sprintf(
'The exponent %d is not in the range 0 to %d.',
$exponent,
Calculator::MAX_POWER
));
}
return new BigDecimal(Calculator::get()->pow($this->value, $exponent), $this->scale * $exponent);
}
/**
* Returns the quotient of the division of this number by this given one.
*
* The quotient has a scale of `0`.
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @return BigDecimal The quotient.
*
* @throws MathException If the divisor is not a valid decimal number, or is zero.
*/
public function quotient($that) : BigDecimal
{
$that = BigDecimal::of($that);
if ($that->isZero()) {
throw DivisionByZeroException::divisionByZero();
}
$p = $this->valueWithMinScale($that->scale);
$q = $that->valueWithMinScale($this->scale);
$quotient = Calculator::get()->divQ($p, $q);
return new BigDecimal($quotient, 0);
}
/**
* Returns the remainder of the division of this number by this given one.
*
* The remainder has a scale of `max($this->scale, $that->scale)`.
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @return BigDecimal The remainder.
*
* @throws MathException If the divisor is not a valid decimal number, or is zero.
*/
public function remainder($that) : BigDecimal
{
$that = BigDecimal::of($that);
if ($that->isZero()) {
throw DivisionByZeroException::divisionByZero();
}
$p = $this->valueWithMinScale($that->scale);
$q = $that->valueWithMinScale($this->scale);
$remainder = Calculator::get()->divR($p, $q);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
return new BigDecimal($remainder, $scale);
}
/**
* Returns the quotient and remainder of the division of this number by the given one.
*
* The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`.
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @return BigDecimal[] An array containing the quotient and the remainder.
*
* @throws MathException If the divisor is not a valid decimal number, or is zero.
*/
public function quotientAndRemainder($that) : array
{
$that = BigDecimal::of($that);
if ($that->isZero()) {
throw DivisionByZeroException::divisionByZero();
}
$p = $this->valueWithMinScale($that->scale);
$q = $that->valueWithMinScale($this->scale);
[$quotient, $remainder] = Calculator::get()->divQR($p, $q);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
$quotient = new BigDecimal($quotient, 0);
$remainder = new BigDecimal($remainder, $scale);
return [$quotient, $remainder];
}
/**
* Returns the square root of this number, rounded down to the given number of decimals.
*
* @param int $scale
*
* @return BigDecimal
*
* @throws \InvalidArgumentException If the scale is negative.
* @throws NegativeNumberException If this number is negative.
*/
public function sqrt(int $scale) : BigDecimal
{
if ($scale < 0) {
throw new \InvalidArgumentException('Scale cannot be negative.');
}
if ($this->value === '0') {
return new BigDecimal('0', $scale);
}
if ($this->value[0] === '-') {
throw new NegativeNumberException('Cannot calculate the square root of a negative number.');
}
$value = $this->value;
$addDigits = 2 * $scale - $this->scale;
if ($addDigits > 0) {
// add zeros
$value .= \str_repeat('0', $addDigits);
} elseif ($addDigits < 0) {
// trim digits
if (-$addDigits >= \strlen($this->value)) {
// requesting a scale too low, will always yield a zero result
return new BigDecimal('0', $scale);
}
$value = \substr($value, 0, $addDigits);
}
$value = Calculator::get()->sqrt($value);
return new BigDecimal($value, $scale);
}
/**
* Returns a copy of this BigDecimal with the decimal point moved $n places to the left.
*
* @param int $n
*
* @return BigDecimal
*/
public function withPointMovedLeft(int $n) : BigDecimal
{
if ($n === 0) {
return $this;
}
if ($n < 0) {
return $this->withPointMovedRight(-$n);
}
return new BigDecimal($this->value, $this->scale + $n);
}
/**
* Returns a copy of this BigDecimal with the decimal point moved $n places to the right.
*
* @param int $n
*
* @return BigDecimal
*/
public function withPointMovedRight(int $n) : BigDecimal
{
if ($n === 0) {
return $this;
}
if ($n < 0) {
return $this->withPointMovedLeft(-$n);
}
$value = $this->value;
$scale = $this->scale - $n;
if ($scale < 0) {
if ($value !== '0') {
$value .= \str_repeat('0', -$scale);
}
$scale = 0;
}
return new BigDecimal($value, $scale);
}
/**
* Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
*
* @return BigDecimal
*/
public function stripTrailingZeros() : BigDecimal
{
if ($this->scale === 0) {
return $this;
}
$trimmedValue = \rtrim($this->value, '0');
if ($trimmedValue === '') {
return BigDecimal::zero();
}
$trimmableZeros = \strlen($this->value) - \strlen($trimmedValue);
if ($trimmableZeros === 0) {
return $this;
}
if ($trimmableZeros > $this->scale) {
$trimmableZeros = $this->scale;
}
$value = \substr($this->value, 0, -$trimmableZeros);
$scale = $this->scale - $trimmableZeros;
return new BigDecimal($value, $scale);
}
/**
* Returns the absolute value of this number.
*
* @return BigDecimal
*/
public function abs() : BigDecimal
{
return $this->isNegative() ? $this->negated() : $this;
}
/**
* Returns the negated value of this number.
*
* @return BigDecimal
*/
public function negated() : BigDecimal
{
return new BigDecimal(Calculator::get()->neg($this->value), $this->scale);
}
/**
* {@inheritdoc}
*/
public function compareTo($that) : int
{
$that = BigNumber::of($that);
if ($that instanceof BigInteger) {
$that = $that->toBigDecimal();
}
if ($that instanceof BigDecimal) {
[$a, $b] = $this->scaleValues($this, $that);
return Calculator::get()->cmp($a, $b);
}
return - $that->compareTo($this);
}
/**
* {@inheritdoc}
*/
public function getSign() : int
{
return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
}
/**
* @return BigInteger
*/
public function getUnscaledValue() : BigInteger
{
return BigInteger::create($this->value);
}
/**
* @return int
*/
public function getScale() : int
{
return $this->scale;
}
/**
* Returns a string representing the integral part of this decimal number.
*
* Example: `-123.456` => `-123`.
*
* @return string
*/
public function getIntegralPart() : string
{
if ($this->scale === 0) {
return $this->value;
}
$value = $this->getUnscaledValueWithLeadingZeros();
return \substr($value, 0, -$this->scale);
}
/**
* Returns a string representing the fractional part of this decimal number.
*
* If the scale is zero, an empty string is returned.
*
* Examples: `-123.456` => '456', `123` => ''.
*
* @return string
*/
public function getFractionalPart() : string
{
if ($this->scale === 0) {
return '';
}
$value = $this->getUnscaledValueWithLeadingZeros();
return \substr($value, -$this->scale);
}
/**
* Returns whether this decimal number has a non-zero fractional part.
*
* @return bool
*/
public function hasNonZeroFractionalPart() : bool
{
return $this->getFractionalPart() !== \str_repeat('0', $this->scale);
}
/**
* {@inheritdoc}
*/
public function toBigInteger() : BigInteger
{
$zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0);
return BigInteger::create($zeroScaleDecimal->value);
}
/**
* {@inheritdoc}
*/
public function toBigDecimal() : BigDecimal
{
return $this;
}
/**
* {@inheritdoc}
*/
public function toBigRational() : BigRational
{
$numerator = BigInteger::create($this->value);
$denominator = BigInteger::create('1' . \str_repeat('0', $this->scale));
return BigRational::create($numerator, $denominator, false);
}
/**
* {@inheritdoc}
*/
public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
{
if ($scale === $this->scale) {
return $this;
}
return $this->dividedBy(BigDecimal::one(), $scale, $roundingMode);
}
/**
* {@inheritdoc}
*/
public function toInt() : int
{
return $this->toBigInteger()->toInt();
}
/**
* {@inheritdoc}
*/
public function toFloat() : float
{
return (float) (string) $this;
}
/**
* {@inheritdoc}
*/
public function __toString() : string
{
if ($this->scale === 0) {
return $this->value;
}
$value = $this->getUnscaledValueWithLeadingZeros();
return \substr($value, 0, -$this->scale) . '.' . \substr($value, -$this->scale);
}
/**
* This method is required for serializing the object and SHOULD NOT be accessed directly.
*
* @internal
*
* @return array{value: string, scale: int}
*/
public function __serialize(): array
{
return ['value' => $this->value, 'scale' => $this->scale];
}
/**
* This method is only here to allow unserializing the object and cannot be accessed directly.
*
* @internal
* @psalm-suppress RedundantPropertyInitializationCheck
*
* @param array{value: string, scale: int} $data
*
* @return void
*
* @throws \LogicException
*/
public function __unserialize(array $data): void
{
if (isset($this->value)) {
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
}
$this->value = $data['value'];
$this->scale = $data['scale'];
}
/**
* This method is required by interface Serializable and SHOULD NOT be accessed directly.
*
* @internal
*
* @return string
*/
public function serialize() : string
{
return $this->value . ':' . $this->scale;
}
/**
* This method is only here to implement interface Serializable and cannot be accessed directly.
*
* @internal
* @psalm-suppress RedundantPropertyInitializationCheck
*
* @param string $value
*
* @return void
*
* @throws \LogicException
*/
public function unserialize($value) : void
{
if (isset($this->value)) {
throw new \LogicException('unserialize() is an internal function, it must not be called directly.');
}
[$value, $scale] = \explode(':', $value);
$this->value = $value;
$this->scale = (int) $scale;
}
/**
* Puts the internal values of the given decimal numbers on the same scale.
*
* @param BigDecimal $x The first decimal number.
* @param BigDecimal $y The second decimal number.
*
* @return array{string, string} The scaled integer values of $x and $y.
*/
private function scaleValues(BigDecimal $x, BigDecimal $y) : array
{
$a = $x->value;
$b = $y->value;
if ($b !== '0' && $x->scale > $y->scale) {
$b .= \str_repeat('0', $x->scale - $y->scale);
} elseif ($a !== '0' && $x->scale < $y->scale) {
$a .= \str_repeat('0', $y->scale - $x->scale);
}
return [$a, $b];
}
/**
* @param int $scale
*
* @return string
*/
private function valueWithMinScale(int $scale) : string
{
$value = $this->value;
if ($this->value !== '0' && $scale > $this->scale) {
$value .= \str_repeat('0', $scale - $this->scale);
}
return $value;
}
/**
* Adds leading zeros if necessary to the unscaled value to represent the full decimal number.
*
* @return string
*/
private function getUnscaledValueWithLeadingZeros() : string
{
$value = $this->value;
$targetLength = $this->scale + 1;
$negative = ($value[0] === '-');
$length = \strlen($value);
if ($negative) {
$length--;
}
if ($length >= $targetLength) {
return $this->value;
}
if ($negative) {
$value = \substr($value, 1);
}
$value = \str_pad($value, $targetLength, '0', STR_PAD_LEFT);
if ($negative) {
$value = '-' . $value;
}
return $value;
}
}

1197
vendor/brick/math/src/BigInteger.php vendored Normal file

File diff suppressed because it is too large Load Diff

570
vendor/brick/math/src/BigNumber.php vendored Normal file
View File

@@ -0,0 +1,570 @@
<?php
declare(strict_types=1);
namespace Brick\Math;
use Brick\Math\Exception\DivisionByZeroException;
use Brick\Math\Exception\MathException;
use Brick\Math\Exception\NumberFormatException;
use Brick\Math\Exception\RoundingNecessaryException;
/**
* Common interface for arbitrary-precision rational numbers.
*
* @psalm-immutable
*/
abstract class BigNumber implements \Serializable, \JsonSerializable
{
/**
* The regular expression used to parse integer, decimal and rational numbers.
*/
private const PARSE_REGEXP =
'/^' .
'(?<sign>[\-\+])?' .
'(?:' .
'(?:' .
'(?<integral>[0-9]+)?' .
'(?<point>\.)?' .
'(?<fractional>[0-9]+)?' .
'(?:[eE](?<exponent>[\-\+]?[0-9]+))?' .
')|(?:' .
'(?<numerator>[0-9]+)' .
'\/?' .
'(?<denominator>[0-9]+)' .
')' .
')' .
'$/';
/**
* Creates a BigNumber of the given value.
*
* The concrete return type is dependent on the given value, with the following rules:
*
* - BigNumber instances are returned as is
* - integer numbers are returned as BigInteger
* - floating point numbers are converted to a string then parsed as such
* - strings containing a `/` character are returned as BigRational
* - strings containing a `.` character or using an exponential notation are returned as BigDecimal
* - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger
*
* @param BigNumber|int|float|string $value
*
* @return BigNumber
*
* @throws NumberFormatException If the format of the number is not valid.
* @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
*
* @psalm-pure
*/
public static function of($value) : BigNumber
{
if ($value instanceof BigNumber) {
return $value;
}
if (\is_int($value)) {
return new BigInteger((string) $value);
}
/** @psalm-suppress RedundantCastGivenDocblockType We cannot trust the untyped $value here! */
$value = \is_float($value) ? self::floatToString($value) : (string) $value;
$throw = static function() use ($value) : void {
throw new NumberFormatException(\sprintf(
'The given value "%s" does not represent a valid number.',
$value
));
};
if (\preg_match(self::PARSE_REGEXP, $value, $matches) !== 1) {
$throw();
}
$getMatch = static fn(string $value): ?string => (($matches[$value] ?? '') !== '') ? $matches[$value] : null;
$sign = $getMatch('sign');
$numerator = $getMatch('numerator');
$denominator = $getMatch('denominator');
if ($numerator !== null) {
assert($denominator !== null);
if ($sign !== null) {
$numerator = $sign . $numerator;
}
$numerator = self::cleanUp($numerator);
$denominator = self::cleanUp($denominator);
if ($denominator === '0') {
throw DivisionByZeroException::denominatorMustNotBeZero();
}
return new BigRational(
new BigInteger($numerator),
new BigInteger($denominator),
false
);
}
$point = $getMatch('point');
$integral = $getMatch('integral');
$fractional = $getMatch('fractional');
$exponent = $getMatch('exponent');
if ($integral === null && $fractional === null) {
$throw();
}
if ($integral === null) {
$integral = '0';
}
if ($point !== null || $exponent !== null) {
$fractional = ($fractional ?? '');
$exponent = ($exponent !== null) ? (int) $exponent : 0;
if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) {
throw new NumberFormatException('Exponent too large.');
}
$unscaledValue = self::cleanUp(($sign ?? ''). $integral . $fractional);
$scale = \strlen($fractional) - $exponent;
if ($scale < 0) {
if ($unscaledValue !== '0') {
$unscaledValue .= \str_repeat('0', - $scale);
}
$scale = 0;
}
return new BigDecimal($unscaledValue, $scale);
}
$integral = self::cleanUp(($sign ?? '') . $integral);
return new BigInteger($integral);
}
/**
* Safely converts float to string, avoiding locale-dependent issues.
*
* @see https://github.com/brick/math/pull/20
*
* @param float $float
*
* @return string
*
* @psalm-pure
* @psalm-suppress ImpureFunctionCall
*/
private static function floatToString(float $float) : string
{
$currentLocale = \setlocale(LC_NUMERIC, '0');
\setlocale(LC_NUMERIC, 'C');
$result = (string) $float;
\setlocale(LC_NUMERIC, $currentLocale);
return $result;
}
/**
* Proxy method to access protected constructors from sibling classes.
*
* @internal
*
* @param mixed ...$args The arguments to the constructor.
*
* @return static
*
* @psalm-pure
* @psalm-suppress TooManyArguments
* @psalm-suppress UnsafeInstantiation
*/
protected static function create(... $args) : BigNumber
{
return new static(... $args);
}
/**
* Returns the minimum of the given values.
*
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
* to an instance of the class this method is called on.
*
* @return static The minimum value.
*
* @throws \InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
*
* @psalm-suppress LessSpecificReturnStatement
* @psalm-suppress MoreSpecificReturnType
* @psalm-pure
*/
public static function min(...$values) : BigNumber
{
$min = null;
foreach ($values as $value) {
$value = static::of($value);
if ($min === null || $value->isLessThan($min)) {
$min = $value;
}
}
if ($min === null) {
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
}
return $min;
}
/**
* Returns the maximum of the given values.
*
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
* to an instance of the class this method is called on.
*
* @return static The maximum value.
*
* @throws \InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
*
* @psalm-suppress LessSpecificReturnStatement
* @psalm-suppress MoreSpecificReturnType
* @psalm-pure
*/
public static function max(...$values) : BigNumber
{
$max = null;
foreach ($values as $value) {
$value = static::of($value);
if ($max === null || $value->isGreaterThan($max)) {
$max = $value;
}
}
if ($max === null) {
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
}
return $max;
}
/**
* Returns the sum of the given values.
*
* @param BigNumber|int|float|string ...$values The numbers to add. All the numbers need to be convertible
* to an instance of the class this method is called on.
*
* @return static The sum.
*
* @throws \InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
*
* @psalm-suppress LessSpecificReturnStatement
* @psalm-suppress MoreSpecificReturnType
* @psalm-pure
*/
public static function sum(...$values) : BigNumber
{
/** @var BigNumber|null $sum */
$sum = null;
foreach ($values as $value) {
$value = static::of($value);
$sum = $sum === null ? $value : self::add($sum, $value);
}
if ($sum === null) {
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
}
return $sum;
}
/**
* Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException.
*
* @todo This could be better resolved by creating an abstract protected method in BigNumber, and leaving to
* concrete classes the responsibility to perform the addition themselves or delegate it to the given number,
* depending on their ability to perform the operation. This will also require a version bump because we're
* potentially breaking custom BigNumber implementations (if any...)
*
* @param BigNumber $a
* @param BigNumber $b
*
* @return BigNumber
*
* @psalm-pure
*/
private static function add(BigNumber $a, BigNumber $b) : BigNumber
{
if ($a instanceof BigRational) {
return $a->plus($b);
}
if ($b instanceof BigRational) {
return $b->plus($a);
}
if ($a instanceof BigDecimal) {
return $a->plus($b);
}
if ($b instanceof BigDecimal) {
return $b->plus($a);
}
/** @var BigInteger $a */
return $a->plus($b);
}
/**
* Removes optional leading zeros and + sign from the given number.
*
* @param string $number The number, validated as a non-empty string of digits with optional leading sign.
*
* @return string
*
* @psalm-pure
*/
private static function cleanUp(string $number) : string
{
$firstChar = $number[0];
if ($firstChar === '+' || $firstChar === '-') {
$number = \substr($number, 1);
}
$number = \ltrim($number, '0');
if ($number === '') {
return '0';
}
if ($firstChar === '-') {
return '-' . $number;
}
return $number;
}
/**
* Checks if this number is equal to the given one.
*
* @param BigNumber|int|float|string $that
*
* @return bool
*/
public function isEqualTo($that) : bool
{
return $this->compareTo($that) === 0;
}
/**
* Checks if this number is strictly lower than the given one.
*
* @param BigNumber|int|float|string $that
*
* @return bool
*/
public function isLessThan($that) : bool
{
return $this->compareTo($that) < 0;
}
/**
* Checks if this number is lower than or equal to the given one.
*
* @param BigNumber|int|float|string $that
*
* @return bool
*/
public function isLessThanOrEqualTo($that) : bool
{
return $this->compareTo($that) <= 0;
}
/**
* Checks if this number is strictly greater than the given one.
*
* @param BigNumber|int|float|string $that
*
* @return bool
*/
public function isGreaterThan($that) : bool
{
return $this->compareTo($that) > 0;
}
/**
* Checks if this number is greater than or equal to the given one.
*
* @param BigNumber|int|float|string $that
*
* @return bool
*/
public function isGreaterThanOrEqualTo($that) : bool
{
return $this->compareTo($that) >= 0;
}
/**
* Checks if this number equals zero.
*
* @return bool
*/
public function isZero() : bool
{
return $this->getSign() === 0;
}
/**
* Checks if this number is strictly negative.
*
* @return bool
*/
public function isNegative() : bool
{
return $this->getSign() < 0;
}
/**
* Checks if this number is negative or zero.
*
* @return bool
*/
public function isNegativeOrZero() : bool
{
return $this->getSign() <= 0;
}
/**
* Checks if this number is strictly positive.
*
* @return bool
*/
public function isPositive() : bool
{
return $this->getSign() > 0;
}
/**
* Checks if this number is positive or zero.
*
* @return bool
*/
public function isPositiveOrZero() : bool
{
return $this->getSign() >= 0;
}
/**
* Returns the sign of this number.
*
* @return int -1 if the number is negative, 0 if zero, 1 if positive.
*/
abstract public function getSign() : int;
/**
* Compares this number to the given one.
*
* @param BigNumber|int|float|string $that
*
* @return int [-1,0,1] If `$this` is lower than, equal to, or greater than `$that`.
*
* @throws MathException If the number is not valid.
*/
abstract public function compareTo($that) : int;
/**
* Converts this number to a BigInteger.
*
* @return BigInteger The converted number.
*
* @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding.
*/
abstract public function toBigInteger() : BigInteger;
/**
* Converts this number to a BigDecimal.
*
* @return BigDecimal The converted number.
*
* @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding.
*/
abstract public function toBigDecimal() : BigDecimal;
/**
* Converts this number to a BigRational.
*
* @return BigRational The converted number.
*/
abstract public function toBigRational() : BigRational;
/**
* Converts this number to a BigDecimal with the given scale, using rounding if necessary.
*
* @param int $scale The scale of the resulting `BigDecimal`.
* @param int $roundingMode A `RoundingMode` constant.
*
* @return BigDecimal
*
* @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding.
* This only applies when RoundingMode::UNNECESSARY is used.
*/
abstract public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal;
/**
* Returns the exact value of this number as a native integer.
*
* If this number cannot be converted to a native integer without losing precision, an exception is thrown.
* Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit.
*
* @return int The converted value.
*
* @throws MathException If this number cannot be exactly converted to a native integer.
*/
abstract public function toInt() : int;
/**
* Returns an approximation of this number as a floating-point value.
*
* Note that this method can discard information as the precision of a floating-point value
* is inherently limited.
*
* If the number is greater than the largest representable floating point number, positive infinity is returned.
* If the number is less than the smallest representable floating point number, negative infinity is returned.
*
* @return float The converted value.
*/
abstract public function toFloat() : float;
/**
* Returns a string representation of this number.
*
* The output of this method can be parsed by the `of()` factory method;
* this will yield an object equal to this one, without any information loss.
*
* @return string
*/
abstract public function __toString() : string;
/**
* {@inheritdoc}
*/
public function jsonSerialize() : string
{
return $this->__toString();
}
}

520
vendor/brick/math/src/BigRational.php vendored Normal file
View File

@@ -0,0 +1,520 @@
<?php
declare(strict_types=1);
namespace Brick\Math;
use Brick\Math\Exception\DivisionByZeroException;
use Brick\Math\Exception\MathException;
use Brick\Math\Exception\NumberFormatException;
use Brick\Math\Exception\RoundingNecessaryException;
/**
* An arbitrarily large rational number.
*
* This class is immutable.
*
* @psalm-immutable
*/
final class BigRational extends BigNumber
{
/**
* The numerator.
*/
private BigInteger $numerator;
/**
* The denominator. Always strictly positive.
*/
private BigInteger $denominator;
/**
* Protected constructor. Use a factory method to obtain an instance.
*
* @param BigInteger $numerator The numerator.
* @param BigInteger $denominator The denominator.
* @param bool $checkDenominator Whether to check the denominator for negative and zero.
*
* @throws DivisionByZeroException If the denominator is zero.
*/
protected function __construct(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator)
{
if ($checkDenominator) {
if ($denominator->isZero()) {
throw DivisionByZeroException::denominatorMustNotBeZero();
}
if ($denominator->isNegative()) {
$numerator = $numerator->negated();
$denominator = $denominator->negated();
}
}
$this->numerator = $numerator;
$this->denominator = $denominator;
}
/**
* Creates a BigRational of the given value.
*
* @param BigNumber|int|float|string $value
*
* @return BigRational
*
* @throws MathException If the value cannot be converted to a BigRational.
*
* @psalm-pure
*/
public static function of($value) : BigNumber
{
return parent::of($value)->toBigRational();
}
/**
* Creates a BigRational out of a numerator and a denominator.
*
* If the denominator is negative, the signs of both the numerator and the denominator
* will be inverted to ensure that the denominator is always positive.
*
* @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger.
* @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger.
*
* @return BigRational
*
* @throws NumberFormatException If an argument does not represent a valid number.
* @throws RoundingNecessaryException If an argument represents a non-integer number.
* @throws DivisionByZeroException If the denominator is zero.
*
* @psalm-pure
*/
public static function nd($numerator, $denominator) : BigRational
{
$numerator = BigInteger::of($numerator);
$denominator = BigInteger::of($denominator);
return new BigRational($numerator, $denominator, true);
}
/**
* Returns a BigRational representing zero.
*
* @return BigRational
*
* @psalm-pure
*/
public static function zero() : BigRational
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigRational|null $zero
*/
static $zero;
if ($zero === null) {
$zero = new BigRational(BigInteger::zero(), BigInteger::one(), false);
}
return $zero;
}
/**
* Returns a BigRational representing one.
*
* @return BigRational
*
* @psalm-pure
*/
public static function one() : BigRational
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigRational|null $one
*/
static $one;
if ($one === null) {
$one = new BigRational(BigInteger::one(), BigInteger::one(), false);
}
return $one;
}
/**
* Returns a BigRational representing ten.
*
* @return BigRational
*
* @psalm-pure
*/
public static function ten() : BigRational
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigRational|null $ten
*/
static $ten;
if ($ten === null) {
$ten = new BigRational(BigInteger::ten(), BigInteger::one(), false);
}
return $ten;
}
/**
* @return BigInteger
*/
public function getNumerator() : BigInteger
{
return $this->numerator;
}
/**
* @return BigInteger
*/
public function getDenominator() : BigInteger
{
return $this->denominator;
}
/**
* Returns the quotient of the division of the numerator by the denominator.
*
* @return BigInteger
*/
public function quotient() : BigInteger
{
return $this->numerator->quotient($this->denominator);
}
/**
* Returns the remainder of the division of the numerator by the denominator.
*
* @return BigInteger
*/
public function remainder() : BigInteger
{
return $this->numerator->remainder($this->denominator);
}
/**
* Returns the quotient and remainder of the division of the numerator by the denominator.
*
* @return BigInteger[]
*/
public function quotientAndRemainder() : array
{
return $this->numerator->quotientAndRemainder($this->denominator);
}
/**
* Returns the sum of this number and the given one.
*
* @param BigNumber|int|float|string $that The number to add.
*
* @return BigRational The result.
*
* @throws MathException If the number is not valid.
*/
public function plus($that) : BigRational
{
$that = BigRational::of($that);
$numerator = $this->numerator->multipliedBy($that->denominator);
$numerator = $numerator->plus($that->numerator->multipliedBy($this->denominator));
$denominator = $this->denominator->multipliedBy($that->denominator);
return new BigRational($numerator, $denominator, false);
}
/**
* Returns the difference of this number and the given one.
*
* @param BigNumber|int|float|string $that The number to subtract.
*
* @return BigRational The result.
*
* @throws MathException If the number is not valid.
*/
public function minus($that) : BigRational
{
$that = BigRational::of($that);
$numerator = $this->numerator->multipliedBy($that->denominator);
$numerator = $numerator->minus($that->numerator->multipliedBy($this->denominator));
$denominator = $this->denominator->multipliedBy($that->denominator);
return new BigRational($numerator, $denominator, false);
}
/**
* Returns the product of this number and the given one.
*
* @param BigNumber|int|float|string $that The multiplier.
*
* @return BigRational The result.
*
* @throws MathException If the multiplier is not a valid number.
*/
public function multipliedBy($that) : BigRational
{
$that = BigRational::of($that);
$numerator = $this->numerator->multipliedBy($that->numerator);
$denominator = $this->denominator->multipliedBy($that->denominator);
return new BigRational($numerator, $denominator, false);
}
/**
* Returns the result of the division of this number by the given one.
*
* @param BigNumber|int|float|string $that The divisor.
*
* @return BigRational The result.
*
* @throws MathException If the divisor is not a valid number, or is zero.
*/
public function dividedBy($that) : BigRational
{
$that = BigRational::of($that);
$numerator = $this->numerator->multipliedBy($that->denominator);
$denominator = $this->denominator->multipliedBy($that->numerator);
return new BigRational($numerator, $denominator, true);
}
/**
* Returns this number exponentiated to the given value.
*
* @param int $exponent The exponent.
*
* @return BigRational The result.
*
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
*/
public function power(int $exponent) : BigRational
{
if ($exponent === 0) {
$one = BigInteger::one();
return new BigRational($one, $one, false);
}
if ($exponent === 1) {
return $this;
}
return new BigRational(
$this->numerator->power($exponent),
$this->denominator->power($exponent),
false
);
}
/**
* Returns the reciprocal of this BigRational.
*
* The reciprocal has the numerator and denominator swapped.
*
* @return BigRational
*
* @throws DivisionByZeroException If the numerator is zero.
*/
public function reciprocal() : BigRational
{
return new BigRational($this->denominator, $this->numerator, true);
}
/**
* Returns the absolute value of this BigRational.
*
* @return BigRational
*/
public function abs() : BigRational
{
return new BigRational($this->numerator->abs(), $this->denominator, false);
}
/**
* Returns the negated value of this BigRational.
*
* @return BigRational
*/
public function negated() : BigRational
{
return new BigRational($this->numerator->negated(), $this->denominator, false);
}
/**
* Returns the simplified value of this BigRational.
*
* @return BigRational
*/
public function simplified() : BigRational
{
$gcd = $this->numerator->gcd($this->denominator);
$numerator = $this->numerator->quotient($gcd);
$denominator = $this->denominator->quotient($gcd);
return new BigRational($numerator, $denominator, false);
}
/**
* {@inheritdoc}
*/
public function compareTo($that) : int
{
return $this->minus($that)->getSign();
}
/**
* {@inheritdoc}
*/
public function getSign() : int
{
return $this->numerator->getSign();
}
/**
* {@inheritdoc}
*/
public function toBigInteger() : BigInteger
{
$simplified = $this->simplified();
if (! $simplified->denominator->isEqualTo(1)) {
throw new RoundingNecessaryException('This rational number cannot be represented as an integer value without rounding.');
}
return $simplified->numerator;
}
/**
* {@inheritdoc}
*/
public function toBigDecimal() : BigDecimal
{
return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator);
}
/**
* {@inheritdoc}
*/
public function toBigRational() : BigRational
{
return $this;
}
/**
* {@inheritdoc}
*/
public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
{
return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode);
}
/**
* {@inheritdoc}
*/
public function toInt() : int
{
return $this->toBigInteger()->toInt();
}
/**
* {@inheritdoc}
*/
public function toFloat() : float
{
$simplified = $this->simplified();
return $simplified->numerator->toFloat() / $simplified->denominator->toFloat();
}
/**
* {@inheritdoc}
*/
public function __toString() : string
{
$numerator = (string) $this->numerator;
$denominator = (string) $this->denominator;
if ($denominator === '1') {
return $numerator;
}
return $this->numerator . '/' . $this->denominator;
}
/**
* This method is required for serializing the object and SHOULD NOT be accessed directly.
*
* @internal
*
* @return array{numerator: BigInteger, denominator: BigInteger}
*/
public function __serialize(): array
{
return ['numerator' => $this->numerator, 'denominator' => $this->denominator];
}
/**
* This method is only here to allow unserializing the object and cannot be accessed directly.
*
* @internal
* @psalm-suppress RedundantPropertyInitializationCheck
*
* @param array{numerator: BigInteger, denominator: BigInteger} $data
*
* @return void
*
* @throws \LogicException
*/
public function __unserialize(array $data): void
{
if (isset($this->numerator)) {
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
}
$this->numerator = $data['numerator'];
$this->denominator = $data['denominator'];
}
/**
* This method is required by interface Serializable and SHOULD NOT be accessed directly.
*
* @internal
*
* @return string
*/
public function serialize() : string
{
return $this->numerator . '/' . $this->denominator;
}
/**
* This method is only here to implement interface Serializable and cannot be accessed directly.
*
* @internal
* @psalm-suppress RedundantPropertyInitializationCheck
*
* @param string $value
*
* @return void
*
* @throws \LogicException
*/
public function unserialize($value) : void
{
if (isset($this->numerator)) {
throw new \LogicException('unserialize() is an internal function, it must not be called directly.');
}
[$numerator, $denominator] = \explode('/', $value);
$this->numerator = BigInteger::of($numerator);
$this->denominator = BigInteger::of($denominator);
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Exception;
/**
* Exception thrown when a division by zero occurs.
*/
class DivisionByZeroException extends MathException
{
/**
* @return DivisionByZeroException
*
* @psalm-pure
*/
public static function divisionByZero() : DivisionByZeroException
{
return new self('Division by zero.');
}
/**
* @return DivisionByZeroException
*
* @psalm-pure
*/
public static function modulusMustNotBeZero() : DivisionByZeroException
{
return new self('The modulus must not be zero.');
}
/**
* @return DivisionByZeroException
*
* @psalm-pure
*/
public static function denominatorMustNotBeZero() : DivisionByZeroException
{
return new self('The denominator of a rational number cannot be zero.');
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Exception;
use Brick\Math\BigInteger;
/**
* Exception thrown when an integer overflow occurs.
*/
class IntegerOverflowException extends MathException
{
/**
* @param BigInteger $value
*
* @return IntegerOverflowException
*
* @psalm-pure
*/
public static function toIntOverflow(BigInteger $value) : IntegerOverflowException
{
$message = '%s is out of range %d to %d and cannot be represented as an integer.';
return new self(\sprintf($message, (string) $value, PHP_INT_MIN, PHP_INT_MAX));
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Exception;
/**
* Base class for all math exceptions.
*
* This class is abstract to ensure that only fine-grained exceptions are thrown throughout the code.
*/
class MathException extends \RuntimeException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Exception;
/**
* Exception thrown when attempting to perform an unsupported operation, such as a square root, on a negative number.
*/
class NegativeNumberException extends MathException
{
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Exception;
/**
* Exception thrown when attempting to create a number from a string with an invalid format.
*/
class NumberFormatException extends MathException
{
/**
* @param string $char The failing character.
*
* @return NumberFormatException
*
* @psalm-pure
*/
public static function charNotInAlphabet(string $char) : self
{
$ord = \ord($char);
if ($ord < 32 || $ord > 126) {
$char = \strtoupper(\dechex($ord));
if ($ord < 10) {
$char = '0' . $char;
}
} else {
$char = '"' . $char . '"';
}
return new self(sprintf('Char %s is not a valid character in the given alphabet.', $char));
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Exception;
/**
* Exception thrown when a number cannot be represented at the requested scale without rounding.
*/
class RoundingNecessaryException extends MathException
{
/**
* @return RoundingNecessaryException
*
* @psalm-pure
*/
public static function roundingNecessary() : RoundingNecessaryException
{
return new self('Rounding is necessary to represent the result of the operation at this scale.');
}
}

View File

@@ -0,0 +1,751 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Internal;
use Brick\Math\Exception\RoundingNecessaryException;
use Brick\Math\RoundingMode;
/**
* Performs basic operations on arbitrary size integers.
*
* Unless otherwise specified, all parameters must be validated as non-empty strings of digits,
* without leading zero, and with an optional leading minus sign if the number is not zero.
*
* Any other parameter format will lead to undefined behaviour.
* All methods must return strings respecting this format, unless specified otherwise.
*
* @internal
*
* @psalm-immutable
*/
abstract class Calculator
{
/**
* The maximum exponent value allowed for the pow() method.
*/
public const MAX_POWER = 1000000;
/**
* The alphabet for converting from and to base 2 to 36, lowercase.
*/
public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
/**
* The Calculator instance in use.
*/
private static ?Calculator $instance = null;
/**
* Sets the Calculator instance to use.
*
* An instance is typically set only in unit tests: the autodetect is usually the best option.
*
* @param Calculator|null $calculator The calculator instance, or NULL to revert to autodetect.
*
* @return void
*/
final public static function set(?Calculator $calculator) : void
{
self::$instance = $calculator;
}
/**
* Returns the Calculator instance to use.
*
* If none has been explicitly set, the fastest available implementation will be returned.
*
* @return Calculator
*
* @psalm-pure
* @psalm-suppress ImpureStaticProperty
*/
final public static function get() : Calculator
{
if (self::$instance === null) {
/** @psalm-suppress ImpureMethodCall */
self::$instance = self::detect();
}
return self::$instance;
}
/**
* Returns the fastest available Calculator implementation.
*
* @codeCoverageIgnore
*
* @return Calculator
*/
private static function detect() : Calculator
{
if (\extension_loaded('gmp')) {
return new Calculator\GmpCalculator();
}
if (\extension_loaded('bcmath')) {
return new Calculator\BcMathCalculator();
}
return new Calculator\NativeCalculator();
}
/**
* Extracts the sign & digits of the operands.
*
* @param string $a The first operand.
* @param string $b The second operand.
*
* @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits.
*/
final protected function init(string $a, string $b) : array
{
return [
$aNeg = ($a[0] === '-'),
$bNeg = ($b[0] === '-'),
$aNeg ? \substr($a, 1) : $a,
$bNeg ? \substr($b, 1) : $b,
];
}
/**
* Returns the absolute value of a number.
*
* @param string $n The number.
*
* @return string The absolute value.
*/
final public function abs(string $n) : string
{
return ($n[0] === '-') ? \substr($n, 1) : $n;
}
/**
* Negates a number.
*
* @param string $n The number.
*
* @return string The negated value.
*/
final public function neg(string $n) : string
{
if ($n === '0') {
return '0';
}
if ($n[0] === '-') {
return \substr($n, 1);
}
return '-' . $n;
}
/**
* Compares two numbers.
*
* @param string $a The first number.
* @param string $b The second number.
*
* @return int [-1, 0, 1] If the first number is less than, equal to, or greater than the second number.
*/
final public function cmp(string $a, string $b) : int
{
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
if ($aNeg && ! $bNeg) {
return -1;
}
if ($bNeg && ! $aNeg) {
return 1;
}
$aLen = \strlen($aDig);
$bLen = \strlen($bDig);
if ($aLen < $bLen) {
$result = -1;
} elseif ($aLen > $bLen) {
$result = 1;
} else {
$result = $aDig <=> $bDig;
}
return $aNeg ? -$result : $result;
}
/**
* Adds two numbers.
*
* @param string $a The augend.
* @param string $b The addend.
*
* @return string The sum.
*/
abstract public function add(string $a, string $b) : string;
/**
* Subtracts two numbers.
*
* @param string $a The minuend.
* @param string $b The subtrahend.
*
* @return string The difference.
*/
abstract public function sub(string $a, string $b) : string;
/**
* Multiplies two numbers.
*
* @param string $a The multiplicand.
* @param string $b The multiplier.
*
* @return string The product.
*/
abstract public function mul(string $a, string $b) : string;
/**
* Returns the quotient of the division of two numbers.
*
* @param string $a The dividend.
* @param string $b The divisor, must not be zero.
*
* @return string The quotient.
*/
abstract public function divQ(string $a, string $b) : string;
/**
* Returns the remainder of the division of two numbers.
*
* @param string $a The dividend.
* @param string $b The divisor, must not be zero.
*
* @return string The remainder.
*/
abstract public function divR(string $a, string $b) : string;
/**
* Returns the quotient and remainder of the division of two numbers.
*
* @param string $a The dividend.
* @param string $b The divisor, must not be zero.
*
* @return array{string, string} An array containing the quotient and remainder.
*/
abstract public function divQR(string $a, string $b) : array;
/**
* Exponentiates a number.
*
* @param string $a The base number.
* @param int $e The exponent, validated as an integer between 0 and MAX_POWER.
*
* @return string The power.
*/
abstract public function pow(string $a, int $e) : string;
/**
* @param string $a
* @param string $b The modulus; must not be zero.
*
* @return string
*/
public function mod(string $a, string $b) : string
{
return $this->divR($this->add($this->divR($a, $b), $b), $b);
}
/**
* Returns the modular multiplicative inverse of $x modulo $m.
*
* If $x has no multiplicative inverse mod m, this method must return null.
*
* This method can be overridden by the concrete implementation if the underlying library has built-in support.
*
* @param string $x
* @param string $m The modulus; must not be negative or zero.
*
* @return string|null
*/
public function modInverse(string $x, string $m) : ?string
{
if ($m === '1') {
return '0';
}
$modVal = $x;
if ($x[0] === '-' || ($this->cmp($this->abs($x), $m) >= 0)) {
$modVal = $this->mod($x, $m);
}
[$g, $x] = $this->gcdExtended($modVal, $m);
if ($g !== '1') {
return null;
}
return $this->mod($this->add($this->mod($x, $m), $m), $m);
}
/**
* Raises a number into power with modulo.
*
* @param string $base The base number; must be positive or zero.
* @param string $exp The exponent; must be positive or zero.
* @param string $mod The modulus; must be strictly positive.
*
* @return string The power.
*/
abstract public function modPow(string $base, string $exp, string $mod) : string;
/**
* Returns the greatest common divisor of the two numbers.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for GCD calculations.
*
* @param string $a The first number.
* @param string $b The second number.
*
* @return string The GCD, always positive, or zero if both arguments are zero.
*/
public function gcd(string $a, string $b) : string
{
if ($a === '0') {
return $this->abs($b);
}
if ($b === '0') {
return $this->abs($a);
}
return $this->gcd($b, $this->divR($a, $b));
}
/**
* @return array{string, string, string} GCD, X, Y
*/
private function gcdExtended(string $a, string $b) : array
{
if ($a === '0') {
return [$b, '0', '1'];
}
[$gcd, $x1, $y1] = $this->gcdExtended($this->mod($b, $a), $a);
$x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1));
$y = $x1;
return [$gcd, $x, $y];
}
/**
* Returns the square root of the given number, rounded down.
*
* The result is the largest x such that x² ≤ n.
* The input MUST NOT be negative.
*
* @param string $n The number.
*
* @return string The square root.
*/
abstract public function sqrt(string $n) : string;
/**
* Converts a number from an arbitrary base.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for base conversion.
*
* @param string $number The number, positive or zero, non-empty, case-insensitively validated for the given base.
* @param int $base The base of the number, validated from 2 to 36.
*
* @return string The converted number, following the Calculator conventions.
*/
public function fromBase(string $number, int $base) : string
{
return $this->fromArbitraryBase(\strtolower($number), self::ALPHABET, $base);
}
/**
* Converts a number to an arbitrary base.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for base conversion.
*
* @param string $number The number to convert, following the Calculator conventions.
* @param int $base The base to convert to, validated from 2 to 36.
*
* @return string The converted number, lowercase.
*/
public function toBase(string $number, int $base) : string
{
$negative = ($number[0] === '-');
if ($negative) {
$number = \substr($number, 1);
}
$number = $this->toArbitraryBase($number, self::ALPHABET, $base);
if ($negative) {
return '-' . $number;
}
return $number;
}
/**
* Converts a non-negative number in an arbitrary base using a custom alphabet, to base 10.
*
* @param string $number The number to convert, validated as a non-empty string,
* containing only chars in the given alphabet/base.
* @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum.
* @param int $base The base of the number, validated from 2 to alphabet length.
*
* @return string The number in base 10, following the Calculator conventions.
*/
final public function fromArbitraryBase(string $number, string $alphabet, int $base) : string
{
// remove leading "zeros"
$number = \ltrim($number, $alphabet[0]);
if ($number === '') {
return '0';
}
// optimize for "one"
if ($number === $alphabet[1]) {
return '1';
}
$result = '0';
$power = '1';
$base = (string) $base;
for ($i = \strlen($number) - 1; $i >= 0; $i--) {
$index = \strpos($alphabet, $number[$i]);
if ($index !== 0) {
$result = $this->add($result, ($index === 1)
? $power
: $this->mul($power, (string) $index)
);
}
if ($i !== 0) {
$power = $this->mul($power, $base);
}
}
return $result;
}
/**
* Converts a non-negative number to an arbitrary base using a custom alphabet.
*
* @param string $number The number to convert, positive or zero, following the Calculator conventions.
* @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum.
* @param int $base The base to convert to, validated from 2 to alphabet length.
*
* @return string The converted number in the given alphabet.
*/
final public function toArbitraryBase(string $number, string $alphabet, int $base) : string
{
if ($number === '0') {
return $alphabet[0];
}
$base = (string) $base;
$result = '';
while ($number !== '0') {
[$number, $remainder] = $this->divQR($number, $base);
$remainder = (int) $remainder;
$result .= $alphabet[$remainder];
}
return \strrev($result);
}
/**
* Performs a rounded division.
*
* Rounding is performed when the remainder of the division is not zero.
*
* @param string $a The dividend.
* @param string $b The divisor, must not be zero.
* @param int $roundingMode The rounding mode.
*
* @return string
*
* @throws \InvalidArgumentException If the rounding mode is invalid.
* @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary.
*
* @psalm-suppress ImpureFunctionCall
*/
final public function divRound(string $a, string $b, int $roundingMode) : string
{
[$quotient, $remainder] = $this->divQR($a, $b);
$hasDiscardedFraction = ($remainder !== '0');
$isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-');
$discardedFractionSign = function() use ($remainder, $b) : int {
$r = $this->abs($this->mul($remainder, '2'));
$b = $this->abs($b);
return $this->cmp($r, $b);
};
$increment = false;
switch ($roundingMode) {
case RoundingMode::UNNECESSARY:
if ($hasDiscardedFraction) {
throw RoundingNecessaryException::roundingNecessary();
}
break;
case RoundingMode::UP:
$increment = $hasDiscardedFraction;
break;
case RoundingMode::DOWN:
break;
case RoundingMode::CEILING:
$increment = $hasDiscardedFraction && $isPositiveOrZero;
break;
case RoundingMode::FLOOR:
$increment = $hasDiscardedFraction && ! $isPositiveOrZero;
break;
case RoundingMode::HALF_UP:
$increment = $discardedFractionSign() >= 0;
break;
case RoundingMode::HALF_DOWN:
$increment = $discardedFractionSign() > 0;
break;
case RoundingMode::HALF_CEILING:
$increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0;
break;
case RoundingMode::HALF_FLOOR:
$increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
break;
case RoundingMode::HALF_EVEN:
$lastDigit = (int) $quotient[-1];
$lastDigitIsEven = ($lastDigit % 2 === 0);
$increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
break;
default:
throw new \InvalidArgumentException('Invalid rounding mode.');
}
if ($increment) {
return $this->add($quotient, $isPositiveOrZero ? '1' : '-1');
}
return $quotient;
}
/**
* Calculates bitwise AND of two numbers.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for bitwise operations.
*
* @param string $a
* @param string $b
*
* @return string
*/
public function and(string $a, string $b) : string
{
return $this->bitwise('and', $a, $b);
}
/**
* Calculates bitwise OR of two numbers.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for bitwise operations.
*
* @param string $a
* @param string $b
*
* @return string
*/
public function or(string $a, string $b) : string
{
return $this->bitwise('or', $a, $b);
}
/**
* Calculates bitwise XOR of two numbers.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for bitwise operations.
*
* @param string $a
* @param string $b
*
* @return string
*/
public function xor(string $a, string $b) : string
{
return $this->bitwise('xor', $a, $b);
}
/**
* Performs a bitwise operation on a decimal number.
*
* @param 'and'|'or'|'xor' $operator The operator to use.
* @param string $a The left operand.
* @param string $b The right operand.
*
* @return string
*/
private function bitwise(string $operator, string $a, string $b) : string
{
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
$aBin = $this->toBinary($aDig);
$bBin = $this->toBinary($bDig);
$aLen = \strlen($aBin);
$bLen = \strlen($bBin);
if ($aLen > $bLen) {
$bBin = \str_repeat("\x00", $aLen - $bLen) . $bBin;
} elseif ($bLen > $aLen) {
$aBin = \str_repeat("\x00", $bLen - $aLen) . $aBin;
}
if ($aNeg) {
$aBin = $this->twosComplement($aBin);
}
if ($bNeg) {
$bBin = $this->twosComplement($bBin);
}
switch ($operator) {
case 'and':
$value = $aBin & $bBin;
$negative = ($aNeg and $bNeg);
break;
case 'or':
$value = $aBin | $bBin;
$negative = ($aNeg or $bNeg);
break;
case 'xor':
$value = $aBin ^ $bBin;
$negative = ($aNeg xor $bNeg);
break;
// @codeCoverageIgnoreStart
default:
throw new \InvalidArgumentException('Invalid bitwise operator.');
// @codeCoverageIgnoreEnd
}
if ($negative) {
$value = $this->twosComplement($value);
}
$result = $this->toDecimal($value);
return $negative ? $this->neg($result) : $result;
}
/**
* @param string $number A positive, binary number.
*
* @return string
*/
private function twosComplement(string $number) : string
{
$xor = \str_repeat("\xff", \strlen($number));
$number ^= $xor;
for ($i = \strlen($number) - 1; $i >= 0; $i--) {
$byte = \ord($number[$i]);
if (++$byte !== 256) {
$number[$i] = \chr($byte);
break;
}
$number[$i] = "\x00";
if ($i === 0) {
$number = "\x01" . $number;
}
}
return $number;
}
/**
* Converts a decimal number to a binary string.
*
* @param string $number The number to convert, positive or zero, only digits.
*
* @return string
*/
private function toBinary(string $number) : string
{
$result = '';
while ($number !== '0') {
[$number, $remainder] = $this->divQR($number, '256');
$result .= \chr((int) $remainder);
}
return \strrev($result);
}
/**
* Returns the positive decimal representation of a binary number.
*
* @param string $bytes The bytes representing the number.
*
* @return string
*/
private function toDecimal(string $bytes) : string
{
$result = '0';
$power = '1';
for ($i = \strlen($bytes) - 1; $i >= 0; $i--) {
$index = \ord($bytes[$i]);
if ($index !== 0) {
$result = $this->add($result, ($index === 1)
? $power
: $this->mul($power, (string) $index)
);
}
if ($i !== 0) {
$power = $this->mul($power, '256');
}
}
return $result;
}
}

View File

@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Internal\Calculator;
use Brick\Math\Internal\Calculator;
/**
* Calculator implementation built around the bcmath library.
*
* @internal
*
* @psalm-immutable
*/
class BcMathCalculator extends Calculator
{
/**
* {@inheritdoc}
*/
public function add(string $a, string $b) : string
{
return \bcadd($a, $b, 0);
}
/**
* {@inheritdoc}
*/
public function sub(string $a, string $b) : string
{
return \bcsub($a, $b, 0);
}
/**
* {@inheritdoc}
*/
public function mul(string $a, string $b) : string
{
return \bcmul($a, $b, 0);
}
/**
* {@inheritdoc}
*
* @psalm-suppress InvalidNullableReturnType
* @psalm-suppress NullableReturnStatement
*/
public function divQ(string $a, string $b) : string
{
return \bcdiv($a, $b, 0);
}
/**
* {@inheritdoc}
*
* @psalm-suppress InvalidNullableReturnType
* @psalm-suppress NullableReturnStatement
*/
public function divR(string $a, string $b) : string
{
if (version_compare(PHP_VERSION, '7.2') >= 0) {
return \bcmod($a, $b, 0);
}
return \bcmod($a, $b);
}
/**
* {@inheritdoc}
*/
public function divQR(string $a, string $b) : array
{
$q = \bcdiv($a, $b, 0);
if (version_compare(PHP_VERSION, '7.2') >= 0) {
$r = \bcmod($a, $b, 0);
} else {
$r = \bcmod($a, $b);
}
assert($q !== null);
assert($r !== null);
return [$q, $r];
}
/**
* {@inheritdoc}
*/
public function pow(string $a, int $e) : string
{
return \bcpow($a, (string) $e, 0);
}
/**
* {@inheritdoc}
*/
public function modPow(string $base, string $exp, string $mod) : string
{
return \bcpowmod($base, $exp, $mod, 0);
}
/**
* {@inheritDoc}
*/
public function sqrt(string $n) : string
{
return \bcsqrt($n, 0);
}
}

View File

@@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Internal\Calculator;
use Brick\Math\Internal\Calculator;
/**
* Calculator implementation built around the GMP library.
*
* @internal
*
* @psalm-immutable
*/
class GmpCalculator extends Calculator
{
/**
* {@inheritdoc}
*/
public function add(string $a, string $b) : string
{
return \gmp_strval(\gmp_add($a, $b));
}
/**
* {@inheritdoc}
*/
public function sub(string $a, string $b) : string
{
return \gmp_strval(\gmp_sub($a, $b));
}
/**
* {@inheritdoc}
*/
public function mul(string $a, string $b) : string
{
return \gmp_strval(\gmp_mul($a, $b));
}
/**
* {@inheritdoc}
*/
public function divQ(string $a, string $b) : string
{
return \gmp_strval(\gmp_div_q($a, $b));
}
/**
* {@inheritdoc}
*/
public function divR(string $a, string $b) : string
{
return \gmp_strval(\gmp_div_r($a, $b));
}
/**
* {@inheritdoc}
*/
public function divQR(string $a, string $b) : array
{
[$q, $r] = \gmp_div_qr($a, $b);
return [
\gmp_strval($q),
\gmp_strval($r)
];
}
/**
* {@inheritdoc}
*/
public function pow(string $a, int $e) : string
{
return \gmp_strval(\gmp_pow($a, $e));
}
/**
* {@inheritdoc}
*/
public function modInverse(string $x, string $m) : ?string
{
$result = \gmp_invert($x, $m);
if ($result === false) {
return null;
}
return \gmp_strval($result);
}
/**
* {@inheritdoc}
*/
public function modPow(string $base, string $exp, string $mod) : string
{
return \gmp_strval(\gmp_powm($base, $exp, $mod));
}
/**
* {@inheritdoc}
*/
public function gcd(string $a, string $b) : string
{
return \gmp_strval(\gmp_gcd($a, $b));
}
/**
* {@inheritdoc}
*/
public function fromBase(string $number, int $base) : string
{
return \gmp_strval(\gmp_init($number, $base));
}
/**
* {@inheritdoc}
*/
public function toBase(string $number, int $base) : string
{
return \gmp_strval($number, $base);
}
/**
* {@inheritdoc}
*/
public function and(string $a, string $b) : string
{
return \gmp_strval(\gmp_and($a, $b));
}
/**
* {@inheritdoc}
*/
public function or(string $a, string $b) : string
{
return \gmp_strval(\gmp_or($a, $b));
}
/**
* {@inheritdoc}
*/
public function xor(string $a, string $b) : string
{
return \gmp_strval(\gmp_xor($a, $b));
}
/**
* {@inheritDoc}
*/
public function sqrt(string $n) : string
{
return \gmp_strval(\gmp_sqrt($n));
}
}

View File

@@ -0,0 +1,632 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Internal\Calculator;
use Brick\Math\Internal\Calculator;
/**
* Calculator implementation using only native PHP code.
*
* @internal
*
* @psalm-immutable
*/
class NativeCalculator extends Calculator
{
/**
* The max number of digits the platform can natively add, subtract, multiply or divide without overflow.
* For multiplication, this represents the max sum of the lengths of both operands.
*
* For addition, it is assumed that an extra digit can hold a carry (1) without overflowing.
* Example: 32-bit: max number 1,999,999,999 (9 digits + carry)
* 64-bit: max number 1,999,999,999,999,999,999 (18 digits + carry)
*/
private int $maxDigits;
/**
* Class constructor.
*
* @codeCoverageIgnore
*/
public function __construct()
{
switch (PHP_INT_SIZE) {
case 4:
$this->maxDigits = 9;
break;
case 8:
$this->maxDigits = 18;
break;
default:
throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.');
}
}
/**
* {@inheritdoc}
*/
public function add(string $a, string $b) : string
{
/**
* @psalm-var numeric-string $a
* @psalm-var numeric-string $b
*/
$result = $a + $b;
if (is_int($result)) {
return (string) $result;
}
if ($a === '0') {
return $b;
}
if ($b === '0') {
return $a;
}
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
$result = $aNeg === $bNeg ? $this->doAdd($aDig, $bDig) : $this->doSub($aDig, $bDig);
if ($aNeg) {
$result = $this->neg($result);
}
return $result;
}
/**
* {@inheritdoc}
*/
public function sub(string $a, string $b) : string
{
return $this->add($a, $this->neg($b));
}
/**
* {@inheritdoc}
*/
public function mul(string $a, string $b) : string
{
/**
* @psalm-var numeric-string $a
* @psalm-var numeric-string $b
*/
$result = $a * $b;
if (is_int($result)) {
return (string) $result;
}
if ($a === '0' || $b === '0') {
return '0';
}
if ($a === '1') {
return $b;
}
if ($b === '1') {
return $a;
}
if ($a === '-1') {
return $this->neg($b);
}
if ($b === '-1') {
return $this->neg($a);
}
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
$result = $this->doMul($aDig, $bDig);
if ($aNeg !== $bNeg) {
$result = $this->neg($result);
}
return $result;
}
/**
* {@inheritdoc}
*/
public function divQ(string $a, string $b) : string
{
return $this->divQR($a, $b)[0];
}
/**
* {@inheritdoc}
*/
public function divR(string $a, string $b): string
{
return $this->divQR($a, $b)[1];
}
/**
* {@inheritdoc}
*/
public function divQR(string $a, string $b) : array
{
if ($a === '0') {
return ['0', '0'];
}
if ($a === $b) {
return ['1', '0'];
}
if ($b === '1') {
return [$a, '0'];
}
if ($b === '-1') {
return [$this->neg($a), '0'];
}
/** @psalm-var numeric-string $a */
$na = $a * 1; // cast to number
if (is_int($na)) {
/** @psalm-var numeric-string $b */
$nb = $b * 1;
if (is_int($nb)) {
// the only division that may overflow is PHP_INT_MIN / -1,
// which cannot happen here as we've already handled a divisor of -1 above.
$r = $na % $nb;
$q = ($na - $r) / $nb;
assert(is_int($q));
return [
(string) $q,
(string) $r
];
}
}
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
[$q, $r] = $this->doDiv($aDig, $bDig);
if ($aNeg !== $bNeg) {
$q = $this->neg($q);
}
if ($aNeg) {
$r = $this->neg($r);
}
return [$q, $r];
}
/**
* {@inheritdoc}
*/
public function pow(string $a, int $e) : string
{
if ($e === 0) {
return '1';
}
if ($e === 1) {
return $a;
}
$odd = $e % 2;
$e -= $odd;
$aa = $this->mul($a, $a);
/** @psalm-suppress PossiblyInvalidArgument We're sure that $e / 2 is an int now */
$result = $this->pow($aa, $e / 2);
if ($odd === 1) {
$result = $this->mul($result, $a);
}
return $result;
}
/**
* Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/
*
* {@inheritdoc}
*/
public function modPow(string $base, string $exp, string $mod) : string
{
// special case: the algorithm below fails with 0 power 0 mod 1 (returns 1 instead of 0)
if ($base === '0' && $exp === '0' && $mod === '1') {
return '0';
}
// special case: the algorithm below fails with power 0 mod 1 (returns 1 instead of 0)
if ($exp === '0' && $mod === '1') {
return '0';
}
$x = $base;
$res = '1';
// numbers are positive, so we can use remainder instead of modulo
$x = $this->divR($x, $mod);
while ($exp !== '0') {
if (in_array($exp[-1], ['1', '3', '5', '7', '9'])) { // odd
$res = $this->divR($this->mul($res, $x), $mod);
}
$exp = $this->divQ($exp, '2');
$x = $this->divR($this->mul($x, $x), $mod);
}
return $res;
}
/**
* Adapted from https://cp-algorithms.com/num_methods/roots_newton.html
*
* {@inheritDoc}
*/
public function sqrt(string $n) : string
{
if ($n === '0') {
return '0';
}
// initial approximation
$x = \str_repeat('9', \intdiv(\strlen($n), 2) ?: 1);
$decreased = false;
for (;;) {
$nx = $this->divQ($this->add($x, $this->divQ($n, $x)), '2');
if ($x === $nx || $this->cmp($nx, $x) > 0 && $decreased) {
break;
}
$decreased = $this->cmp($nx, $x) < 0;
$x = $nx;
}
return $x;
}
/**
* Performs the addition of two non-signed large integers.
*
* @param string $a The first operand.
* @param string $b The second operand.
*
* @return string
*/
private function doAdd(string $a, string $b) : string
{
[$a, $b, $length] = $this->pad($a, $b);
$carry = 0;
$result = '';
for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) {
$blockLength = $this->maxDigits;
if ($i < 0) {
$blockLength += $i;
/** @psalm-suppress LoopInvalidation */
$i = 0;
}
/** @psalm-var numeric-string $blockA */
$blockA = \substr($a, $i, $blockLength);
/** @psalm-var numeric-string $blockB */
$blockB = \substr($b, $i, $blockLength);
$sum = (string) ($blockA + $blockB + $carry);
$sumLength = \strlen($sum);
if ($sumLength > $blockLength) {
$sum = \substr($sum, 1);
$carry = 1;
} else {
if ($sumLength < $blockLength) {
$sum = \str_repeat('0', $blockLength - $sumLength) . $sum;
}
$carry = 0;
}
$result = $sum . $result;
if ($i === 0) {
break;
}
}
if ($carry === 1) {
$result = '1' . $result;
}
return $result;
}
/**
* Performs the subtraction of two non-signed large integers.
*
* @param string $a The first operand.
* @param string $b The second operand.
*
* @return string
*/
private function doSub(string $a, string $b) : string
{
if ($a === $b) {
return '0';
}
// Ensure that we always subtract to a positive result: biggest minus smallest.
$cmp = $this->doCmp($a, $b);
$invert = ($cmp === -1);
if ($invert) {
$c = $a;
$a = $b;
$b = $c;
}
[$a, $b, $length] = $this->pad($a, $b);
$carry = 0;
$result = '';
$complement = 10 ** $this->maxDigits;
for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) {
$blockLength = $this->maxDigits;
if ($i < 0) {
$blockLength += $i;
/** @psalm-suppress LoopInvalidation */
$i = 0;
}
/** @psalm-var numeric-string $blockA */
$blockA = \substr($a, $i, $blockLength);
/** @psalm-var numeric-string $blockB */
$blockB = \substr($b, $i, $blockLength);
$sum = $blockA - $blockB - $carry;
if ($sum < 0) {
$sum += $complement;
$carry = 1;
} else {
$carry = 0;
}
$sum = (string) $sum;
$sumLength = \strlen($sum);
if ($sumLength < $blockLength) {
$sum = \str_repeat('0', $blockLength - $sumLength) . $sum;
}
$result = $sum . $result;
if ($i === 0) {
break;
}
}
// Carry cannot be 1 when the loop ends, as a > b
assert($carry === 0);
$result = \ltrim($result, '0');
if ($invert) {
$result = $this->neg($result);
}
return $result;
}
/**
* Performs the multiplication of two non-signed large integers.
*
* @param string $a The first operand.
* @param string $b The second operand.
*
* @return string
*/
private function doMul(string $a, string $b) : string
{
$x = \strlen($a);
$y = \strlen($b);
$maxDigits = \intdiv($this->maxDigits, 2);
$complement = 10 ** $maxDigits;
$result = '0';
for ($i = $x - $maxDigits;; $i -= $maxDigits) {
$blockALength = $maxDigits;
if ($i < 0) {
$blockALength += $i;
/** @psalm-suppress LoopInvalidation */
$i = 0;
}
$blockA = (int) \substr($a, $i, $blockALength);
$line = '';
$carry = 0;
for ($j = $y - $maxDigits;; $j -= $maxDigits) {
$blockBLength = $maxDigits;
if ($j < 0) {
$blockBLength += $j;
/** @psalm-suppress LoopInvalidation */
$j = 0;
}
$blockB = (int) \substr($b, $j, $blockBLength);
$mul = $blockA * $blockB + $carry;
$value = $mul % $complement;
$carry = ($mul - $value) / $complement;
$value = (string) $value;
$value = \str_pad($value, $maxDigits, '0', STR_PAD_LEFT);
$line = $value . $line;
if ($j === 0) {
break;
}
}
if ($carry !== 0) {
$line = $carry . $line;
}
$line = \ltrim($line, '0');
if ($line !== '') {
$line .= \str_repeat('0', $x - $blockALength - $i);
$result = $this->add($result, $line);
}
if ($i === 0) {
break;
}
}
return $result;
}
/**
* Performs the division of two non-signed large integers.
*
* @param string $a The first operand.
* @param string $b The second operand.
*
* @return string[] The quotient and remainder.
*/
private function doDiv(string $a, string $b) : array
{
$cmp = $this->doCmp($a, $b);
if ($cmp === -1) {
return ['0', $a];
}
$x = \strlen($a);
$y = \strlen($b);
// we now know that a >= b && x >= y
$q = '0'; // quotient
$r = $a; // remainder
$z = $y; // focus length, always $y or $y+1
for (;;) {
$focus = \substr($a, 0, $z);
$cmp = $this->doCmp($focus, $b);
if ($cmp === -1) {
if ($z === $x) { // remainder < dividend
break;
}
$z++;
}
$zeros = \str_repeat('0', $x - $z);
$q = $this->add($q, '1' . $zeros);
$a = $this->sub($a, $b . $zeros);
$r = $a;
if ($r === '0') { // remainder == 0
break;
}
$x = \strlen($a);
if ($x < $y) { // remainder < dividend
break;
}
$z = $y;
}
return [$q, $r];
}
/**
* Compares two non-signed large numbers.
*
* @param string $a The first operand.
* @param string $b The second operand.
*
* @return int [-1, 0, 1]
*/
private function doCmp(string $a, string $b) : int
{
$x = \strlen($a);
$y = \strlen($b);
$cmp = $x <=> $y;
if ($cmp !== 0) {
return $cmp;
}
return \strcmp($a, $b) <=> 0; // enforce [-1, 0, 1]
}
/**
* Pads the left of one of the given numbers with zeros if necessary to make both numbers the same length.
*
* The numbers must only consist of digits, without leading minus sign.
*
* @param string $a The first operand.
* @param string $b The second operand.
*
* @return array{string, string, int}
*/
private function pad(string $a, string $b) : array
{
$x = \strlen($a);
$y = \strlen($b);
if ($x > $y) {
$b = \str_repeat('0', $x - $y) . $b;
return [$a, $b, $x];
}
if ($x < $y) {
$a = \str_repeat('0', $y - $x) . $a;
return [$a, $b, $y];
}
return [$a, $b, $x];
}
}

107
vendor/brick/math/src/RoundingMode.php vendored Normal file
View File

@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
namespace Brick\Math;
/**
* Specifies a rounding behavior for numerical operations capable of discarding precision.
*
* Each rounding mode indicates how the least significant returned digit of a rounded result
* is to be calculated. If fewer digits are returned than the digits needed to represent the
* exact numerical result, the discarded digits will be referred to as the discarded fraction
* regardless the digits' contribution to the value of the number. In other words, considered
* as a numerical value, the discarded fraction could have an absolute value greater than one.
*/
final class RoundingMode
{
/**
* Private constructor. This class is not instantiable.
*
* @codeCoverageIgnore
*/
private function __construct()
{
}
/**
* Asserts that the requested operation has an exact result, hence no rounding is necessary.
*
* If this rounding mode is specified on an operation that yields a result that
* cannot be represented at the requested scale, a RoundingNecessaryException is thrown.
*/
public const UNNECESSARY = 0;
/**
* Rounds away from zero.
*
* Always increments the digit prior to a nonzero discarded fraction.
* Note that this rounding mode never decreases the magnitude of the calculated value.
*/
public const UP = 1;
/**
* Rounds towards zero.
*
* Never increments the digit prior to a discarded fraction (i.e., truncates).
* Note that this rounding mode never increases the magnitude of the calculated value.
*/
public const DOWN = 2;
/**
* Rounds towards positive infinity.
*
* If the result is positive, behaves as for UP; if negative, behaves as for DOWN.
* Note that this rounding mode never decreases the calculated value.
*/
public const CEILING = 3;
/**
* Rounds towards negative infinity.
*
* If the result is positive, behave as for DOWN; if negative, behave as for UP.
* Note that this rounding mode never increases the calculated value.
*/
public const FLOOR = 4;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.
*
* Behaves as for UP if the discarded fraction is >= 0.5; otherwise, behaves as for DOWN.
* Note that this is the rounding mode commonly taught at school.
*/
public const HALF_UP = 5;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.
*
* Behaves as for UP if the discarded fraction is > 0.5; otherwise, behaves as for DOWN.
*/
public const HALF_DOWN = 6;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards positive infinity.
*
* If the result is positive, behaves as for HALF_UP; if negative, behaves as for HALF_DOWN.
*/
public const HALF_CEILING = 7;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards negative infinity.
*
* If the result is positive, behaves as for HALF_DOWN; if negative, behaves as for HALF_UP.
*/
public const HALF_FLOOR = 8;
/**
* Rounds towards the "nearest neighbor" unless both neighbors are equidistant, in which case rounds towards the even neighbor.
*
* Behaves as for HALF_UP if the digit to the left of the discarded fraction is odd;
* behaves as for HALF_DOWN if it's even.
*
* Note that this is the rounding mode that statistically minimizes
* cumulative error when applied repeatedly over a sequence of calculations.
* It is sometimes known as "Banker's rounding", and is chiefly used in the USA.
*/
public const HALF_EVEN = 9;
}

572
vendor/composer/ClassLoader.php vendored Normal file
View File

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

350
vendor/composer/InstalledVersions.php vendored Normal file
View File

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

21
vendor/composer/LICENSE vendored Normal file
View File

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

5280
vendor/composer/autoload_classmap.php vendored Normal file

File diff suppressed because it is too large Load Diff

35
vendor/composer/autoload_files.php vendored Normal file
View File

@@ -0,0 +1,35 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'3bd81c9b8fcc150b69d8b63b4d2ccf23' => $vendorDir . '/spatie/flare-client-php/src/helpers.php',
'23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'35a6ad97d21e794e7e22a17d806652e4' => $vendorDir . '/nunomaduro/termwind/src/Functions.php',
'801c31d8ed748cfa537fa45402288c95' => $vendorDir . '/psy/psysh/src/functions.php',
'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
'265b4faa2b3a9766332744949e83bf97' => $vendorDir . '/laravel/framework/src/Illuminate/Collections/helpers.php',
'c7a3c339e7e14b60e06a2d7fcce9476b' => $vendorDir . '/laravel/framework/src/Illuminate/Events/functions.php',
'f0906e6318348a765ffb6eb24e0d0938' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/helpers.php',
'58571171fd5812e6e447dce228f52f4d' => $vendorDir . '/laravel/framework/src/Illuminate/Support/helpers.php',
'ec07570ca5a812141189b1fa81503674' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert/Functions.php',
'320163ac6b93aebe3dc25b60a0533d56' => $vendorDir . '/spatie/laravel-ignition/src/helpers.php',
);

10
vendor/composer/autoload_namespaces.php vendored Normal file
View File

@@ -0,0 +1,10 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Mockery' => array($vendorDir . '/mockery/mockery/library'),
);

89
vendor/composer/autoload_psr4.php vendored Normal file
View File

@@ -0,0 +1,89 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'voku\\' => array($vendorDir . '/voku/portable-ascii/src/voku'),
'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/reflection-docblock/src', $vendorDir . '/phpdocumentor/type-resolver/src'),
'Whoops\\' => array($vendorDir . '/filp/whoops/src/Whoops'),
'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
'TijsVerkoyen\\CssToInlineStyles\\' => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'),
'Tests\\' => array($baseDir . '/tests'),
'Termwind\\' => array($vendorDir . '/nunomaduro/termwind/src'),
'Symfony\\Polyfill\\Php81\\' => array($vendorDir . '/symfony/polyfill-php81'),
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'),
'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'),
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'),
'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'),
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'),
'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'),
'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
'Symfony\\Component\\Mime\\' => array($vendorDir . '/symfony/mime'),
'Symfony\\Component\\Mailer\\' => array($vendorDir . '/symfony/mailer'),
'Symfony\\Component\\HttpKernel\\' => array($vendorDir . '/symfony/http-kernel'),
'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'),
'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
'Symfony\\Component\\ErrorHandler\\' => array($vendorDir . '/symfony/error-handler'),
'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'),
'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
'Spatie\\LaravelIgnition\\' => array($vendorDir . '/spatie/laravel-ignition/src'),
'Spatie\\Ignition\\' => array($vendorDir . '/spatie/ignition/src'),
'Spatie\\FlareClient\\' => array($vendorDir . '/spatie/flare-client-php/src'),
'Spatie\\Backtrace\\' => array($vendorDir . '/spatie/backtrace/src'),
'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'),
'Ramsey\\Collection\\' => array($vendorDir . '/ramsey/collection/src'),
'Psy\\' => array($vendorDir . '/psy/psysh/src'),
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'Prophecy\\' => array($vendorDir . '/phpspec/prophecy/src/Prophecy'),
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'),
'NunoMaduro\\Collision\\' => array($vendorDir . '/nunomaduro/collision/src'),
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'),
'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'),
'League\\Config\\' => array($vendorDir . '/league/config/src'),
'League\\CommonMark\\' => array($vendorDir . '/league/commonmark/src'),
'Laravel\\Tinker\\' => array($vendorDir . '/laravel/tinker/src'),
'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'),
'Laravel\\Sanctum\\' => array($vendorDir . '/laravel/sanctum/src'),
'Laravel\\Sail\\' => array($vendorDir . '/laravel/sail/src'),
'Illuminate\\Support\\' => array($vendorDir . '/laravel/framework/src/Illuminate/Macroable', $vendorDir . '/laravel/framework/src/Illuminate/Collections', $vendorDir . '/laravel/framework/src/Illuminate/Conditionable'),
'Illuminate\\' => array($vendorDir . '/laravel/framework/src/Illuminate'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
'GrahamCampbell\\ResultType\\' => array($vendorDir . '/graham-campbell/result-type/src'),
'Fruitcake\\Cors\\' => array($vendorDir . '/fruitcake/php-cors/src'),
'Faker\\' => array($vendorDir . '/fakerphp/faker/src/Faker'),
'Facade\\IgnitionContracts\\' => array($vendorDir . '/facade/ignition-contracts/src'),
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'),
'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'),
'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'),
'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Inflector'),
'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'),
'Dflydev\\DotAccessData\\' => array($vendorDir . '/dflydev/dot-access-data/src'),
'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'),
'Database\\Seeders\\' => array($baseDir . '/database/seeders', $vendorDir . '/laravel/pint/database/seeders'),
'Database\\Factories\\' => array($baseDir . '/database/factories', $vendorDir . '/laravel/pint/database/factories'),
'Cron\\' => array($vendorDir . '/dragonmantank/cron-expression/src/Cron'),
'Carbon\\' => array($vendorDir . '/nesbot/carbon/src/Carbon'),
'Brick\\Math\\' => array($vendorDir . '/brick/math/src'),
'App\\' => array($baseDir . '/app', $vendorDir . '/laravel/pint/app'),
);

57
vendor/composer/autoload_real.php vendored Normal file
View File

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

5800
vendor/composer/autoload_static.php vendored Normal file

File diff suppressed because it is too large Load Diff

8284
vendor/composer/installed.json vendored Normal file

File diff suppressed because it is too large Load Diff

1290
vendor/composer/installed.php vendored Normal file

File diff suppressed because it is too large Load Diff

26
vendor/composer/platform_check.php vendored Normal file
View File

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

View File

@@ -0,0 +1,59 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [3.0.1] - 2021-08-13
### Added
- Adds ReturnTypeWillChange to suppress PHP 8.1 warnings (#40)
## [3.0.0] - 2021-01-01
### Added
- Added support for both `.` and `/`-delimited key paths (#24)
- Added parameter and return types to everything; enabled strict type checks (#18)
- Added new exception classes to better identify certain types of errors (#20)
- `Data` now implements `ArrayAccess` (#17)
- Added ability to merge non-associative array values (#31, #32)
### Changed
- All thrown exceptions are now instances or subclasses of `DataException` (#20)
- Calling `get()` on a missing key path without providing a default will throw a `MissingPathException` instead of returning `null` (#29)
- Bumped supported PHP versions to 7.1 - 8.x (#18)
### Fixed
- Fixed incorrect merging of array values into string values (#32)
- Fixed `get()` method behaving as if keys with `null` values didn't exist
## [2.0.0] - 2017-12-21
### Changed
- Bumped supported PHP versions to 7.0 - 7.4 (#12)
- Switched to PSR-4 autoloading
## [1.1.0] - 2017-01-20
### Added
- Added new `has()` method to check for the existence of the given key (#4, #7)
## [1.0.1] - 2015-08-12
### Added
- Added new optional `$default` parameter to the `get()` method (#2)
## [1.0.0] - 2012-07-17
**Initial release!**
[Unreleased]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.0...main
[3.0.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v2.0.0...v3.0.0
[2.0.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.1.0...v2.0.0
[1.1.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.0.1...v1.1.0
[1.0.1]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/dflydev/dflydev-dot-access-data/releases/tag/v1.0.0

19
vendor/dflydev/dot-access-data/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2012 Dragonfly Development Inc.
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.

158
vendor/dflydev/dot-access-data/README.md vendored Normal file
View File

@@ -0,0 +1,158 @@
Dot Access Data
===============
[![Latest Version](https://img.shields.io/packagist/v/dflydev/dot-access-data.svg?style=flat-square)](https://packagist.org/packages/dflydev/dot-access-data)
[![Total Downloads](https://img.shields.io/packagist/dt/dflydev/dot-access-data.svg?style=flat-square)](https://packagist.org/packages/dflydev/dot-access-data)
[![Software License](https://img.shields.io/badge/License-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Build Status](https://img.shields.io/github/workflow/status/dflydev/dflydev-dot-access-data/Tests/main.svg?style=flat-square)](https://github.com/dflydev/dflydev-dot-access-data/actions?query=workflow%3ATests+branch%3Amain)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/dflydev/dflydev-dot-access-data.svg?style=flat-square)](https://scrutinizer-ci.com/g/dflydev/dflydev-dot-access-data/code-structure/)
[![Quality Score](https://img.shields.io/scrutinizer/g/dflydev/dflydev-dot-access-data.svg?style=flat-square)](https://scrutinizer-ci.com/g/dflydev/dflydev-dot-access-data)
Given a deep data structure, access data by dot notation.
Requirements
------------
* PHP (7.1+)
> For PHP (5.3+) please refer to version `1.0`.
Usage
-----
Abstract example:
```php
use Dflydev\DotAccessData\Data;
$data = new Data;
$data->set('a.b.c', 'C');
$data->set('a.b.d', 'D1');
$data->append('a.b.d', 'D2');
$data->set('a.b.e', ['E0', 'E1', 'E2']);
// C
$data->get('a.b.c');
// ['D1', 'D2']
$data->get('a.b.d');
// ['E0', 'E1', 'E2']
$data->get('a.b.e');
// true
$data->has('a.b.c');
// false
$data->has('a.b.d.j');
// 'some-default-value'
$data->get('some.path.that.does.not.exist', 'some-default-value');
// throws a MissingPathException because no default was given
$data->get('some.path.that.does.not.exist');
```
A more concrete example:
```php
use Dflydev\DotAccessData\Data;
$data = new Data([
'hosts' => [
'hewey' => [
'username' => 'hman',
'password' => 'HPASS',
'roles' => ['web'],
],
'dewey' => [
'username' => 'dman',
'password' => 'D---S',
'roles' => ['web', 'db'],
'nick' => 'dewey dman',
],
'lewey' => [
'username' => 'lman',
'password' => 'LP@$$',
'roles' => ['db'],
],
],
]);
// hman
$username = $data->get('hosts.hewey.username');
// HPASS
$password = $data->get('hosts.hewey.password');
// ['web']
$roles = $data->get('hosts.hewey.roles');
// dewey dman
$nick = $data->get('hosts.dewey.nick');
// Unknown
$nick = $data->get('hosts.lewey.nick', 'Unknown');
// DataInterface instance
$dewey = $data->getData('hosts.dewey');
// dman
$username = $dewey->get('username');
// D---S
$password = $dewey->get('password');
// ['web', 'db']
$roles = $dewey->get('roles');
// No more lewey
$data->remove('hosts.lewey');
// Add DB to hewey's roles
$data->append('hosts.hewey.roles', 'db');
$data->set('hosts.april', [
'username' => 'aman',
'password' => '@---S',
'roles' => ['web'],
]);
// Check if a key exists (true to this case)
$hasKey = $data->has('hosts.dewey.username');
```
`Data` may be used as an array, since it implements `ArrayAccess` interface:
```php
// Get
$data->get('name') === $data['name']; // true
$data['name'] = 'Dewey';
// is equivalent to
$data->set($name, 'Dewey');
isset($data['name']) === $data->has('name');
// Remove key
unset($data['name']);
```
`/` can also be used as a path delimiter:
```php
$data->set('a/b/c', 'd');
echo $data->get('a/b/c'); // "d"
$data->get('a/b/c') === $data->get('a.b.c'); // true
```
License
-------
This library is licensed under the MIT License - see the LICENSE file
for details.
Community
---------
If you have questions or want to help out, join us in the
[#dflydev](irc://irc.freenode.net/#dflydev) channel on irc.freenode.net.

View File

@@ -0,0 +1,67 @@
{
"name": "dflydev/dot-access-data",
"type": "library",
"description": "Given a deep data structure, access data by dot notation.",
"homepage": "https://github.com/dflydev/dflydev-dot-access-data",
"keywords": ["dot", "access", "data", "notation"],
"license": "MIT",
"authors": [
{
"name": "Dragonfly Development Inc.",
"email": "info@dflydev.com",
"homepage": "http://dflydev.com"
},
{
"name": "Beau Simensen",
"email": "beau@dflydev.com",
"homepage": "http://beausimensen.com"
},
{
"name": "Carlos Frutos",
"email": "carlos@kiwing.it",
"homepage": "https://github.com/cfrutos"
},
{
"name": "Colin O'Dell",
"email": "colinodell@gmail.com",
"homepage": "https://www.colinodell.com"
}
],
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^0.12.42",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
"scrutinizer/ocular": "1.6.0",
"squizlabs/php_codesniffer": "^3.5",
"vimeo/psalm": "^3.14"
},
"autoload": {
"psr-4": {
"Dflydev\\DotAccessData\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Dflydev\\DotAccessData\\": "tests/"
}
},
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"scripts": {
"phpcs": "phpcs",
"phpstan": "phpstan analyse",
"phpunit": "phpunit --no-coverage",
"psalm": "psalm",
"test": [
"@phpcs",
"@phpstan",
"@psalm",
"@phpunit"
]
}
}

View File

@@ -0,0 +1,277 @@
<?php
declare(strict_types=1);
/*
* This file is a part of dflydev/dot-access-data.
*
* (c) Dragonfly Development Inc.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Dflydev\DotAccessData;
use ArrayAccess;
use Dflydev\DotAccessData\Exception\DataException;
use Dflydev\DotAccessData\Exception\InvalidPathException;
use Dflydev\DotAccessData\Exception\MissingPathException;
/**
* @implements ArrayAccess<string, mixed>
*/
class Data implements DataInterface, ArrayAccess
{
private const DELIMITERS = ['.', '/'];
/**
* Internal representation of data data
*
* @var array<string, mixed>
*/
protected $data;
/**
* Constructor
*
* @param array<string, mixed> $data
*/
public function __construct(array $data = [])
{
$this->data = $data;
}
/**
* {@inheritdoc}
*/
public function append(string $key, $value = null): void
{
$currentValue =& $this->data;
$keyPath = self::keyToPathArray($key);
$endKey = array_pop($keyPath);
foreach ($keyPath as $currentKey) {
if (! isset($currentValue[$currentKey])) {
$currentValue[$currentKey] = [];
}
$currentValue =& $currentValue[$currentKey];
}
if (!isset($currentValue[$endKey])) {
$currentValue[$endKey] = [];
}
if (!is_array($currentValue[$endKey])) {
// Promote this key to an array.
// TODO: Is this really what we want to do?
$currentValue[$endKey] = [$currentValue[$endKey]];
}
$currentValue[$endKey][] = $value;
}
/**
* {@inheritdoc}
*/
public function set(string $key, $value = null): void
{
$currentValue =& $this->data;
$keyPath = self::keyToPathArray($key);
$endKey = array_pop($keyPath);
foreach ($keyPath as $currentKey) {
if (!isset($currentValue[$currentKey])) {
$currentValue[$currentKey] = [];
}
if (!is_array($currentValue[$currentKey])) {
throw new DataException(sprintf('Key path "%s" within "%s" cannot be indexed into (is not an array)', $currentKey, self::formatPath($key)));
}
$currentValue =& $currentValue[$currentKey];
}
$currentValue[$endKey] = $value;
}
/**
* {@inheritdoc}
*/
public function remove(string $key): void
{
$currentValue =& $this->data;
$keyPath = self::keyToPathArray($key);
$endKey = array_pop($keyPath);
foreach ($keyPath as $currentKey) {
if (!isset($currentValue[$currentKey])) {
return;
}
$currentValue =& $currentValue[$currentKey];
}
unset($currentValue[$endKey]);
}
/**
* {@inheritdoc}
*
* @psalm-mutation-free
*/
public function get(string $key, $default = null)
{
/** @psalm-suppress ImpureFunctionCall */
$hasDefault = \func_num_args() > 1;
$currentValue = $this->data;
$keyPath = self::keyToPathArray($key);
foreach ($keyPath as $currentKey) {
if (!is_array($currentValue) || !array_key_exists($currentKey, $currentValue)) {
if ($hasDefault) {
return $default;
}
throw new MissingPathException($key, sprintf('No data exists at the given path: "%s"', self::formatPath($keyPath)));
}
$currentValue = $currentValue[$currentKey];
}
return $currentValue === null ? $default : $currentValue;
}
/**
* {@inheritdoc}
*
* @psalm-mutation-free
*/
public function has(string $key): bool
{
$currentValue = &$this->data;
foreach (self::keyToPathArray($key) as $currentKey) {
if (
!is_array($currentValue) ||
!array_key_exists($currentKey, $currentValue)
) {
return false;
}
$currentValue = &$currentValue[$currentKey];
}
return true;
}
/**
* {@inheritdoc}
*
* @psalm-mutation-free
*/
public function getData(string $key): DataInterface
{
$value = $this->get($key);
if (is_array($value) && Util::isAssoc($value)) {
return new Data($value);
}
throw new DataException(sprintf('Value at "%s" could not be represented as a DataInterface', self::formatPath($key)));
}
/**
* {@inheritdoc}
*/
public function import(array $data, int $mode = self::REPLACE): void
{
$this->data = Util::mergeAssocArray($this->data, $data, $mode);
}
/**
* {@inheritdoc}
*/
public function importData(DataInterface $data, int $mode = self::REPLACE): void
{
$this->import($data->export(), $mode);
}
/**
* {@inheritdoc}
*
* @psalm-mutation-free
*/
public function export(): array
{
return $this->data;
}
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function offsetExists($key)
{
return $this->has($key);
}
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function offsetGet($key)
{
return $this->get($key, null);
}
/**
* {@inheritdoc}
*
* @param string $key
*/
#[\ReturnTypeWillChange]
public function offsetSet($key, $value)
{
$this->set($key, $value);
}
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function offsetUnset($key)
{
$this->remove($key);
}
/**
* @param string $path
*
* @return string[]
*
* @psalm-return non-empty-list<string>
*
* @psalm-pure
*/
protected static function keyToPathArray(string $path): array
{
if (\strlen($path) === 0) {
throw new InvalidPathException('Path cannot be an empty string');
}
$path = \str_replace(self::DELIMITERS, '.', $path);
return \explode('.', $path);
}
/**
* @param string|string[] $path
*
* @return string
*
* @psalm-pure
*/
protected static function formatPath($path): string
{
if (is_string($path)) {
$path = self::keyToPathArray($path);
}
return implode(' » ', $path);
}
}

View File

@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
/*
* This file is a part of dflydev/dot-access-data.
*
* (c) Dragonfly Development Inc.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Dflydev\DotAccessData;
use Dflydev\DotAccessData\Exception\DataException;
use Dflydev\DotAccessData\Exception\InvalidPathException;
interface DataInterface
{
public const PRESERVE = 0;
public const REPLACE = 1;
public const MERGE = 2;
/**
* Append a value to a key (assumes key refers to an array value)
*
* If the key does not yet exist it will be created.
* If the key references a non-array it's existing contents will be added into a new array before appending the new value.
*
* @param string $key
* @param mixed $value
*
* @throws InvalidPathException if the given key is empty
*/
public function append(string $key, $value = null): void;
/**
* Set a value for a key
*
* If the key does not yet exist it will be created.
*
* @param string $key
* @param mixed $value
*
* @throws InvalidPathException if the given key is empty
* @throws DataException if the given key does not target an array
*/
public function set(string $key, $value = null): void;
/**
* Remove a key
*
* No exception will be thrown if the key does not exist
*
* @param string $key
*
* @throws InvalidPathException if the given key is empty
*/
public function remove(string $key): void;
/**
* Get the raw value for a key
*
* If the key does not exist, an optional default value can be returned instead.
* If no default is provided then an exception will be thrown instead.
*
* @param string $key
* @param mixed $default
*
* @return mixed
*
* @throws InvalidPathException if the given key is empty
* @throws InvalidPathException if the given key does not exist and no default value was given
*
* @psalm-mutation-free
*/
public function get(string $key, $default = null);
/**
* Check if the key exists
*
* @param string $key
*
* @return bool
*
* @throws InvalidPathException if the given key is empty
*
* @psalm-mutation-free
*/
public function has(string $key): bool;
/**
* Get a data instance for a key
*
* @param string $key
*
* @return DataInterface
*
* @throws InvalidPathException if the given key is empty
* @throws DataException if the given key does not reference an array
*
* @psalm-mutation-free
*/
public function getData(string $key): DataInterface;
/**
* Import data into existing data
*
* @param array<string, mixed> $data
* @param self::PRESERVE|self::REPLACE|self::MERGE $mode
*/
public function import(array $data, int $mode = self::REPLACE): void;
/**
* Import data from an external data into existing data
*
* @param DataInterface $data
* @param self::PRESERVE|self::REPLACE|self::MERGE $mode
*/
public function importData(DataInterface $data, int $mode = self::REPLACE): void;
/**
* Export data as raw data
*
* @return array<string, mixed>
*
* @psalm-mutation-free
*/
public function export(): array;
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/*
* This file is a part of dflydev/dot-access-data.
*
* (c) Dragonfly Development Inc.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Dflydev\DotAccessData\Exception;
/**
* Base runtime exception type thrown by this library
*/
class DataException extends \RuntimeException
{
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/*
* This file is a part of dflydev/dot-access-data.
*
* (c) Dragonfly Development Inc.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Dflydev\DotAccessData\Exception;
/**
* Thrown when trying to access an invalid path in the data array
*/
class InvalidPathException extends DataException
{
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/*
* This file is a part of dflydev/dot-access-data.
*
* (c) Dragonfly Development Inc.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Dflydev\DotAccessData\Exception;
use Throwable;
/**
* Thrown when trying to access a path that does not exist
*/
class MissingPathException extends DataException
{
/** @var string */
protected $path;
public function __construct(string $path, string $message = '', int $code = 0, Throwable $previous = null)
{
$this->path = $path;
parent::__construct($message, $code, $previous);
}
public function getPath(): string
{
return $this->path;
}
}

View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
/*
* This file is a part of dflydev/dot-access-data.
*
* (c) Dragonfly Development Inc.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Dflydev\DotAccessData;
class Util
{
/**
* Test if array is an associative array
*
* Note that this function will return true if an array is empty. Meaning
* empty arrays will be treated as if they are associative arrays.
*
* @param array<mixed> $arr
*
* @return bool
*
* @psalm-pure
*/
public static function isAssoc(array $arr): bool
{
return !count($arr) || count(array_filter(array_keys($arr), 'is_string')) == count($arr);
}
/**
* Merge contents from one associtative array to another
*
* @param mixed $to
* @param mixed $from
* @param DataInterface::PRESERVE|DataInterface::REPLACE|DataInterface::MERGE $mode
*
* @return mixed
*
* @psalm-pure
*/
public static function mergeAssocArray($to, $from, int $mode = DataInterface::REPLACE)
{
if ($mode === DataInterface::MERGE && self::isList($to) && self::isList($from)) {
return array_merge($to, $from);
}
if (is_array($from) && is_array($to)) {
foreach ($from as $k => $v) {
if (!isset($to[$k])) {
$to[$k] = $v;
} else {
$to[$k] = self::mergeAssocArray($to[$k], $v, $mode);
}
}
return $to;
}
return $mode === DataInterface::PRESERVE ? $to : $from;
}
/**
* @param mixed $value
*
* @return bool
*
* @psalm-pure
*/
private static function isList($value): bool
{
return is_array($value) && array_values($value) === $value;
}
}

19
vendor/doctrine/inflector/LICENSE vendored Normal file
View File

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

7
vendor/doctrine/inflector/README.md vendored Normal file
View File

@@ -0,0 +1,7 @@
# Doctrine Inflector
Doctrine Inflector is a small library that can perform string manipulations
with regard to uppercase/lowercase and singular/plural forms of words.
[![Build Status](https://github.com/doctrine/inflector/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/inflector/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A4.0.x)
[![Code Coverage](https://codecov.io/gh/doctrine/inflector/branch/2.0.x/graph/badge.svg)](https://codecov.io/gh/doctrine/inflector/branch/2.0.x)

36
vendor/doctrine/inflector/composer.json vendored Normal file
View File

@@ -0,0 +1,36 @@
{
"name": "doctrine/inflector",
"type": "library",
"description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.",
"keywords": ["php", "strings", "words", "manipulation", "inflector", "inflection", "uppercase", "lowercase", "singular", "plural"],
"homepage": "https://www.doctrine-project.org/projects/inflector.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
],
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^8.2",
"phpstan/phpstan": "^0.12",
"phpstan/phpstan-phpunit": "^0.12",
"phpstan/phpstan-strict-rules": "^0.12",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"vimeo/psalm": "^4.10"
},
"autoload": {
"psr-4": {
"Doctrine\\Inflector\\": "lib/Doctrine/Inflector"
}
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\Inflector\\": "tests/Doctrine/Tests/Inflector"
}
}
}

View File

@@ -0,0 +1,226 @@
Introduction
============
The Doctrine Inflector has methods for inflecting text. The features include pluralization,
singularization, converting between camelCase and under_score and capitalizing
words.
Installation
============
You can install the Inflector with composer:
.. code-block:: console
$ composer require doctrine/inflector
Usage
=====
Using the inflector is easy, you can create a new ``Doctrine\Inflector\Inflector`` instance by using
the ``Doctrine\Inflector\InflectorFactory`` class:
.. code-block:: php
use Doctrine\Inflector\InflectorFactory;
$inflector = InflectorFactory::create()->build();
By default it will create an English inflector. If you want to use another language, just pass the language
you want to create an inflector for to the ``createForLanguage()`` method:
.. code-block:: php
use Doctrine\Inflector\InflectorFactory;
use Doctrine\Inflector\Language;
$inflector = InflectorFactory::createForLanguage(Language::SPANISH)->build();
The supported languages are as follows:
- ``Language::ENGLISH``
- ``Language::FRENCH``
- ``Language::NORWEGIAN_BOKMAL``
- ``Language::PORTUGUESE``
- ``Language::SPANISH``
- ``Language::TURKISH``
If you want to manually construct the inflector instead of using a factory, you can do so like this:
.. code-block:: php
use Doctrine\Inflector\CachedWordInflector;
use Doctrine\Inflector\RulesetInflector;
use Doctrine\Inflector\Rules\English;
$inflector = new Inflector(
new CachedWordInflector(new RulesetInflector(
English\Rules::getSingularRuleset()
)),
new CachedWordInflector(new RulesetInflector(
English\Rules::getPluralRuleset()
))
);
Adding Languages
----------------
If you are interested in adding support for your language, take a look at the other languages defined in the
``Doctrine\Inflector\Rules`` namespace and the tests located in ``Doctrine\Tests\Inflector\Rules``. You can copy
one of the languages and update the rules for your language.
Once you have done this, send a pull request to the ``doctrine/inflector`` repository with the additions.
Custom Setup
============
If you want to setup custom singular and plural rules, you can configure these in the factory:
.. code-block:: php
use Doctrine\Inflector\InflectorFactory;
use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Transformations;
use Doctrine\Inflector\Rules\Word;
$inflector = InflectorFactory::create()
->withSingularRules(
new Ruleset(
new Transformations(
new Transformation(new Pattern('/^(bil)er$/i'), '\1'),
new Transformation(new Pattern('/^(inflec|contribu)tors$/i'), '\1ta')
),
new Patterns(new Pattern('singulars')),
new Substitutions(new Substitution(new Word('spins'), new Word('spinor')))
)
)
->withPluralRules(
new Ruleset(
new Transformations(
new Transformation(new Pattern('^(bil)er$'), '\1'),
new Transformation(new Pattern('^(inflec|contribu)tors$'), '\1ta')
),
new Patterns(new Pattern('noflect'), new Pattern('abtuse')),
new Substitutions(
new Substitution(new Word('amaze'), new Word('amazable')),
new Substitution(new Word('phone'), new Word('phonezes'))
)
)
)
->build();
No operation inflector
----------------------
The ``Doctrine\Inflector\NoopWordInflector`` may be used to configure an inflector that doesn't perform any operation for
pluralization and/or singularization. If will simply return the input as output.
This is an implementation of the `Null Object design pattern <https://sourcemaking.com/design_patterns/null_object>`_.
.. code-block:: php
use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\NoopWordInflector;
$inflector = new Inflector(new NoopWordInflector(), new NoopWordInflector());
Tableize
========
Converts ``ModelName`` to ``model_name``:
.. code-block:: php
echo $inflector->tableize('ModelName'); // model_name
Classify
========
Converts ``model_name`` to ``ModelName``:
.. code-block:: php
echo $inflector->classify('model_name'); // ModelName
Camelize
========
This method uses `Classify`_ and then converts the first character to lowercase:
.. code-block:: php
echo $inflector->camelize('model_name'); // modelName
Capitalize
==========
Takes a string and capitalizes all of the words, like PHP's built-in
``ucwords`` function. This extends that behavior, however, by allowing the
word delimiters to be configured, rather than only separating on
whitespace.
Here is an example:
.. code-block:: php
$string = 'top-o-the-morning to all_of_you!';
echo $inflector->capitalize($string); // Top-O-The-Morning To All_of_you!
echo $inflector->capitalize($string, '-_ '); // Top-O-The-Morning To All_Of_You!
Pluralize
=========
Returns a word in plural form.
.. code-block:: php
echo $inflector->pluralize('browser'); // browsers
Singularize
===========
Returns a word in singular form.
.. code-block:: php
echo $inflector->singularize('browsers'); // browser
Urlize
======
Generate a URL friendly string from a string of text:
.. code-block:: php
echo $inflector->urlize('My first blog post'); // my-first-blog-post
Unaccent
========
You can unaccent a string of text using the ``unaccent()`` method:
.. code-block:: php
echo $inflector->unaccent('año'); // ano
Legacy API
==========
The API present in Inflector 1.x is still available, but will be deprecated in a future release and dropped for 3.0.
Support for languages other than English is available in the 2.0 API only.
Acknowledgements
================
The language rules in this library have been adapted from several different sources, including but not limited to:
- `Ruby On Rails Inflector <http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html>`_
- `ICanBoogie Inflector <https://github.com/ICanBoogie/Inflector>`_
- `CakePHP Inflector <https://book.cakephp.org/3.0/en/core-libraries/inflector.html>`_

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
class CachedWordInflector implements WordInflector
{
/** @var WordInflector */
private $wordInflector;
/** @var string[] */
private $cache = [];
public function __construct(WordInflector $wordInflector)
{
$this->wordInflector = $wordInflector;
}
public function inflect(string $word): string
{
return $this->cache[$word] ?? $this->cache[$word] = $this->wordInflector->inflect($word);
}
}

View File

@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
use Doctrine\Inflector\Rules\Ruleset;
use function array_unshift;
abstract class GenericLanguageInflectorFactory implements LanguageInflectorFactory
{
/** @var Ruleset[] */
private $singularRulesets = [];
/** @var Ruleset[] */
private $pluralRulesets = [];
final public function __construct()
{
$this->singularRulesets[] = $this->getSingularRuleset();
$this->pluralRulesets[] = $this->getPluralRuleset();
}
final public function build(): Inflector
{
return new Inflector(
new CachedWordInflector(new RulesetInflector(
...$this->singularRulesets
)),
new CachedWordInflector(new RulesetInflector(
...$this->pluralRulesets
))
);
}
final public function withSingularRules(?Ruleset $singularRules, bool $reset = false): LanguageInflectorFactory
{
if ($reset) {
$this->singularRulesets = [];
}
if ($singularRules instanceof Ruleset) {
array_unshift($this->singularRulesets, $singularRules);
}
return $this;
}
final public function withPluralRules(?Ruleset $pluralRules, bool $reset = false): LanguageInflectorFactory
{
if ($reset) {
$this->pluralRulesets = [];
}
if ($pluralRules instanceof Ruleset) {
array_unshift($this->pluralRulesets, $pluralRules);
}
return $this;
}
abstract protected function getSingularRuleset(): Ruleset;
abstract protected function getPluralRuleset(): Ruleset;
}

View File

@@ -0,0 +1,507 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
use RuntimeException;
use function chr;
use function function_exists;
use function lcfirst;
use function mb_strtolower;
use function ord;
use function preg_match;
use function preg_replace;
use function sprintf;
use function str_replace;
use function strlen;
use function strtolower;
use function strtr;
use function trim;
use function ucwords;
class Inflector
{
private const ACCENTED_CHARACTERS = [
'À' => 'A',
'Á' => 'A',
'Â' => 'A',
'Ã' => 'A',
'Ä' => 'Ae',
'Æ' => 'Ae',
'Å' => 'Aa',
'æ' => 'a',
'Ç' => 'C',
'È' => 'E',
'É' => 'E',
'Ê' => 'E',
'Ë' => 'E',
'Ì' => 'I',
'Í' => 'I',
'Î' => 'I',
'Ï' => 'I',
'Ñ' => 'N',
'Ò' => 'O',
'Ó' => 'O',
'Ô' => 'O',
'Õ' => 'O',
'Ö' => 'Oe',
'Ù' => 'U',
'Ú' => 'U',
'Û' => 'U',
'Ü' => 'Ue',
'Ý' => 'Y',
'ß' => 'ss',
'à' => 'a',
'á' => 'a',
'â' => 'a',
'ã' => 'a',
'ä' => 'ae',
'å' => 'aa',
'ç' => 'c',
'è' => 'e',
'é' => 'e',
'ê' => 'e',
'ë' => 'e',
'ì' => 'i',
'í' => 'i',
'î' => 'i',
'ï' => 'i',
'ñ' => 'n',
'ò' => 'o',
'ó' => 'o',
'ô' => 'o',
'õ' => 'o',
'ö' => 'oe',
'ù' => 'u',
'ú' => 'u',
'û' => 'u',
'ü' => 'ue',
'ý' => 'y',
'ÿ' => 'y',
'Ā' => 'A',
'ā' => 'a',
'Ă' => 'A',
'ă' => 'a',
'Ą' => 'A',
'ą' => 'a',
'Ć' => 'C',
'ć' => 'c',
'Ĉ' => 'C',
'ĉ' => 'c',
'Ċ' => 'C',
'ċ' => 'c',
'Č' => 'C',
'č' => 'c',
'Ď' => 'D',
'ď' => 'd',
'Đ' => 'D',
'đ' => 'd',
'Ē' => 'E',
'ē' => 'e',
'Ĕ' => 'E',
'ĕ' => 'e',
'Ė' => 'E',
'ė' => 'e',
'Ę' => 'E',
'ę' => 'e',
'Ě' => 'E',
'ě' => 'e',
'Ĝ' => 'G',
'ĝ' => 'g',
'Ğ' => 'G',
'ğ' => 'g',
'Ġ' => 'G',
'ġ' => 'g',
'Ģ' => 'G',
'ģ' => 'g',
'Ĥ' => 'H',
'ĥ' => 'h',
'Ħ' => 'H',
'ħ' => 'h',
'Ĩ' => 'I',
'ĩ' => 'i',
'Ī' => 'I',
'ī' => 'i',
'Ĭ' => 'I',
'ĭ' => 'i',
'Į' => 'I',
'į' => 'i',
'İ' => 'I',
'ı' => 'i',
'IJ' => 'IJ',
'ij' => 'ij',
'Ĵ' => 'J',
'ĵ' => 'j',
'Ķ' => 'K',
'ķ' => 'k',
'ĸ' => 'k',
'Ĺ' => 'L',
'ĺ' => 'l',
'Ļ' => 'L',
'ļ' => 'l',
'Ľ' => 'L',
'ľ' => 'l',
'Ŀ' => 'L',
'ŀ' => 'l',
'Ł' => 'L',
'ł' => 'l',
'Ń' => 'N',
'ń' => 'n',
'Ņ' => 'N',
'ņ' => 'n',
'Ň' => 'N',
'ň' => 'n',
'ʼn' => 'N',
'Ŋ' => 'n',
'ŋ' => 'N',
'Ō' => 'O',
'ō' => 'o',
'Ŏ' => 'O',
'ŏ' => 'o',
'Ő' => 'O',
'ő' => 'o',
'Œ' => 'OE',
'œ' => 'oe',
'Ø' => 'O',
'ø' => 'o',
'Ŕ' => 'R',
'ŕ' => 'r',
'Ŗ' => 'R',
'ŗ' => 'r',
'Ř' => 'R',
'ř' => 'r',
'Ś' => 'S',
'ś' => 's',
'Ŝ' => 'S',
'ŝ' => 's',
'Ş' => 'S',
'ş' => 's',
'Š' => 'S',
'š' => 's',
'Ţ' => 'T',
'ţ' => 't',
'Ť' => 'T',
'ť' => 't',
'Ŧ' => 'T',
'ŧ' => 't',
'Ũ' => 'U',
'ũ' => 'u',
'Ū' => 'U',
'ū' => 'u',
'Ŭ' => 'U',
'ŭ' => 'u',
'Ů' => 'U',
'ů' => 'u',
'Ű' => 'U',
'ű' => 'u',
'Ų' => 'U',
'ų' => 'u',
'Ŵ' => 'W',
'ŵ' => 'w',
'Ŷ' => 'Y',
'ŷ' => 'y',
'Ÿ' => 'Y',
'Ź' => 'Z',
'ź' => 'z',
'Ż' => 'Z',
'ż' => 'z',
'Ž' => 'Z',
'ž' => 'z',
'ſ' => 's',
'€' => 'E',
'£' => '',
];
/** @var WordInflector */
private $singularizer;
/** @var WordInflector */
private $pluralizer;
public function __construct(WordInflector $singularizer, WordInflector $pluralizer)
{
$this->singularizer = $singularizer;
$this->pluralizer = $pluralizer;
}
/**
* Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'.
*/
public function tableize(string $word): string
{
$tableized = preg_replace('~(?<=\\w)([A-Z])~u', '_$1', $word);
if ($tableized === null) {
throw new RuntimeException(sprintf(
'preg_replace returned null for value "%s"',
$word
));
}
return mb_strtolower($tableized);
}
/**
* Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'.
*/
public function classify(string $word): string
{
return str_replace([' ', '_', '-'], '', ucwords($word, ' _-'));
}
/**
* Camelizes a word. This uses the classify() method and turns the first character to lowercase.
*/
public function camelize(string $word): string
{
return lcfirst($this->classify($word));
}
/**
* Uppercases words with configurable delimiters between words.
*
* Takes a string and capitalizes all of the words, like PHP's built-in
* ucwords function. This extends that behavior, however, by allowing the
* word delimiters to be configured, rather than only separating on
* whitespace.
*
* Here is an example:
* <code>
* <?php
* $string = 'top-o-the-morning to all_of_you!';
* echo $inflector->capitalize($string);
* // Top-O-The-Morning To All_of_you!
*
* echo $inflector->capitalize($string, '-_ ');
* // Top-O-The-Morning To All_Of_You!
* ?>
* </code>
*
* @param string $string The string to operate on.
* @param string $delimiters A list of word separators.
*
* @return string The string with all delimiter-separated words capitalized.
*/
public function capitalize(string $string, string $delimiters = " \n\t\r\0\x0B-"): string
{
return ucwords($string, $delimiters);
}
/**
* Checks if the given string seems like it has utf8 characters in it.
*
* @param string $string The string to check for utf8 characters in.
*/
public function seemsUtf8(string $string): bool
{
for ($i = 0; $i < strlen($string); $i++) {
if (ord($string[$i]) < 0x80) {
continue; // 0bbbbbbb
}
if ((ord($string[$i]) & 0xE0) === 0xC0) {
$n = 1; // 110bbbbb
} elseif ((ord($string[$i]) & 0xF0) === 0xE0) {
$n = 2; // 1110bbbb
} elseif ((ord($string[$i]) & 0xF8) === 0xF0) {
$n = 3; // 11110bbb
} elseif ((ord($string[$i]) & 0xFC) === 0xF8) {
$n = 4; // 111110bb
} elseif ((ord($string[$i]) & 0xFE) === 0xFC) {
$n = 5; // 1111110b
} else {
return false; // Does not match any model
}
for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
if (++$i === strlen($string) || ((ord($string[$i]) & 0xC0) !== 0x80)) {
return false;
}
}
}
return true;
}
/**
* Remove any illegal characters, accents, etc.
*
* @param string $string String to unaccent
*
* @return string Unaccented string
*/
public function unaccent(string $string): string
{
if (preg_match('/[\x80-\xff]/', $string) === false) {
return $string;
}
if ($this->seemsUtf8($string)) {
$string = strtr($string, self::ACCENTED_CHARACTERS);
} else {
$characters = [];
// Assume ISO-8859-1 if not UTF-8
$characters['in'] =
chr(128)
. chr(131)
. chr(138)
. chr(142)
. chr(154)
. chr(158)
. chr(159)
. chr(162)
. chr(165)
. chr(181)
. chr(192)
. chr(193)
. chr(194)
. chr(195)
. chr(196)
. chr(197)
. chr(199)
. chr(200)
. chr(201)
. chr(202)
. chr(203)
. chr(204)
. chr(205)
. chr(206)
. chr(207)
. chr(209)
. chr(210)
. chr(211)
. chr(212)
. chr(213)
. chr(214)
. chr(216)
. chr(217)
. chr(218)
. chr(219)
. chr(220)
. chr(221)
. chr(224)
. chr(225)
. chr(226)
. chr(227)
. chr(228)
. chr(229)
. chr(231)
. chr(232)
. chr(233)
. chr(234)
. chr(235)
. chr(236)
. chr(237)
. chr(238)
. chr(239)
. chr(241)
. chr(242)
. chr(243)
. chr(244)
. chr(245)
. chr(246)
. chr(248)
. chr(249)
. chr(250)
. chr(251)
. chr(252)
. chr(253)
. chr(255);
$characters['out'] = 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy';
$string = strtr($string, $characters['in'], $characters['out']);
$doubleChars = [];
$doubleChars['in'] = [
chr(140),
chr(156),
chr(198),
chr(208),
chr(222),
chr(223),
chr(230),
chr(240),
chr(254),
];
$doubleChars['out'] = ['OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th'];
$string = str_replace($doubleChars['in'], $doubleChars['out'], $string);
}
return $string;
}
/**
* Convert any passed string to a url friendly string.
* Converts 'My first blog post' to 'my-first-blog-post'
*
* @param string $string String to urlize.
*
* @return string Urlized string.
*/
public function urlize(string $string): string
{
// Remove all non url friendly characters with the unaccent function
$unaccented = $this->unaccent($string);
if (function_exists('mb_strtolower')) {
$lowered = mb_strtolower($unaccented);
} else {
$lowered = strtolower($unaccented);
}
$replacements = [
'/\W/' => ' ',
'/([A-Z]+)([A-Z][a-z])/' => '\1_\2',
'/([a-z\d])([A-Z])/' => '\1_\2',
'/[^A-Z^a-z^0-9^\/]+/' => '-',
];
$urlized = $lowered;
foreach ($replacements as $pattern => $replacement) {
$replaced = preg_replace($pattern, $replacement, $urlized);
if ($replaced === null) {
throw new RuntimeException(sprintf(
'preg_replace returned null for value "%s"',
$urlized
));
}
$urlized = $replaced;
}
return trim($urlized, '-');
}
/**
* Returns a word in singular form.
*
* @param string $word The word in plural form.
*
* @return string The word in singular form.
*/
public function singularize(string $word): string
{
return $this->singularizer->inflect($word);
}
/**
* Returns a word in plural form.
*
* @param string $word The word in singular form.
*
* @return string The word in plural form.
*/
public function pluralize(string $word): string
{
return $this->pluralizer->inflect($word);
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
use Doctrine\Inflector\Rules\English;
use Doctrine\Inflector\Rules\French;
use Doctrine\Inflector\Rules\NorwegianBokmal;
use Doctrine\Inflector\Rules\Portuguese;
use Doctrine\Inflector\Rules\Spanish;
use Doctrine\Inflector\Rules\Turkish;
use InvalidArgumentException;
use function sprintf;
final class InflectorFactory
{
public static function create(): LanguageInflectorFactory
{
return self::createForLanguage(Language::ENGLISH);
}
public static function createForLanguage(string $language): LanguageInflectorFactory
{
switch ($language) {
case Language::ENGLISH:
return new English\InflectorFactory();
case Language::FRENCH:
return new French\InflectorFactory();
case Language::NORWEGIAN_BOKMAL:
return new NorwegianBokmal\InflectorFactory();
case Language::PORTUGUESE:
return new Portuguese\InflectorFactory();
case Language::SPANISH:
return new Spanish\InflectorFactory();
case Language::TURKISH:
return new Turkish\InflectorFactory();
default:
throw new InvalidArgumentException(sprintf(
'Language "%s" is not supported.',
$language
));
}
}
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
final class Language
{
public const ENGLISH = 'english';
public const FRENCH = 'french';
public const NORWEGIAN_BOKMAL = 'norwegian-bokmal';
public const PORTUGUESE = 'portuguese';
public const SPANISH = 'spanish';
public const TURKISH = 'turkish';
private function __construct()
{
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
use Doctrine\Inflector\Rules\Ruleset;
interface LanguageInflectorFactory
{
/**
* Applies custom rules for singularisation
*
* @param bool $reset If true, will unset default inflections for all new rules
*
* @return $this
*/
public function withSingularRules(?Ruleset $singularRules, bool $reset = false): self;
/**
* Applies custom rules for pluralisation
*
* @param bool $reset If true, will unset default inflections for all new rules
*
* @return $this
*/
public function withPluralRules(?Ruleset $pluralRules, bool $reset = false): self;
/**
* Builds the inflector instance with all applicable rules
*/
public function build(): Inflector;
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
class NoopWordInflector implements WordInflector
{
public function inflect(string $word): string
{
return $word;
}
}

View File

@@ -0,0 +1,182 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\English;
use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;
class Inflectible
{
/**
* @return Transformation[]
*/
public static function getSingular(): iterable
{
yield new Transformation(new Pattern('(s)tatuses$'), '\1\2tatus');
yield new Transformation(new Pattern('(s)tatus$'), '\1\2tatus');
yield new Transformation(new Pattern('(c)ampus$'), '\1\2ampus');
yield new Transformation(new Pattern('^(.*)(menu)s$'), '\1\2');
yield new Transformation(new Pattern('(quiz)zes$'), '\\1');
yield new Transformation(new Pattern('(matr)ices$'), '\1ix');
yield new Transformation(new Pattern('(vert|ind)ices$'), '\1ex');
yield new Transformation(new Pattern('^(ox)en'), '\1');
yield new Transformation(new Pattern('(alias)(es)*$'), '\1');
yield new Transformation(new Pattern('(buffal|her|potat|tomat|volcan)oes$'), '\1o');
yield new Transformation(new Pattern('(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$'), '\1us');
yield new Transformation(new Pattern('([ftw]ax)es'), '\1');
yield new Transformation(new Pattern('(analys|ax|cris|test|thes)es$'), '\1is');
yield new Transformation(new Pattern('(shoe|slave)s$'), '\1');
yield new Transformation(new Pattern('(o)es$'), '\1');
yield new Transformation(new Pattern('ouses$'), 'ouse');
yield new Transformation(new Pattern('([^a])uses$'), '\1us');
yield new Transformation(new Pattern('([m|l])ice$'), '\1ouse');
yield new Transformation(new Pattern('(x|ch|ss|sh)es$'), '\1');
yield new Transformation(new Pattern('(m)ovies$'), '\1\2ovie');
yield new Transformation(new Pattern('(s)eries$'), '\1\2eries');
yield new Transformation(new Pattern('([^aeiouy]|qu)ies$'), '\1y');
yield new Transformation(new Pattern('([lr])ves$'), '\1f');
yield new Transformation(new Pattern('(tive)s$'), '\1');
yield new Transformation(new Pattern('(hive)s$'), '\1');
yield new Transformation(new Pattern('(drive)s$'), '\1');
yield new Transformation(new Pattern('(dive)s$'), '\1');
yield new Transformation(new Pattern('(olive)s$'), '\1');
yield new Transformation(new Pattern('([^fo])ves$'), '\1fe');
yield new Transformation(new Pattern('(^analy)ses$'), '\1sis');
yield new Transformation(new Pattern('(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$'), '\1\2sis');
yield new Transformation(new Pattern('(tax)a$'), '\1on');
yield new Transformation(new Pattern('(c)riteria$'), '\1riterion');
yield new Transformation(new Pattern('([ti])a$'), '\1um');
yield new Transformation(new Pattern('(p)eople$'), '\1\2erson');
yield new Transformation(new Pattern('(m)en$'), '\1an');
yield new Transformation(new Pattern('(c)hildren$'), '\1\2hild');
yield new Transformation(new Pattern('(f)eet$'), '\1oot');
yield new Transformation(new Pattern('(n)ews$'), '\1\2ews');
yield new Transformation(new Pattern('eaus$'), 'eau');
yield new Transformation(new Pattern('s$'), '');
}
/**
* @return Transformation[]
*/
public static function getPlural(): iterable
{
yield new Transformation(new Pattern('(s)tatus$'), '\1\2tatuses');
yield new Transformation(new Pattern('(quiz)$'), '\1zes');
yield new Transformation(new Pattern('^(ox)$'), '\1\2en');
yield new Transformation(new Pattern('([m|l])ouse$'), '\1ice');
yield new Transformation(new Pattern('(matr|vert|ind)(ix|ex)$'), '\1ices');
yield new Transformation(new Pattern('(x|ch|ss|sh)$'), '\1es');
yield new Transformation(new Pattern('([^aeiouy]|qu)y$'), '\1ies');
yield new Transformation(new Pattern('(hive|gulf)$'), '\1s');
yield new Transformation(new Pattern('(?:([^f])fe|([lr])f)$'), '\1\2ves');
yield new Transformation(new Pattern('sis$'), 'ses');
yield new Transformation(new Pattern('([ti])um$'), '\1a');
yield new Transformation(new Pattern('(tax)on$'), '\1a');
yield new Transformation(new Pattern('(c)riterion$'), '\1riteria');
yield new Transformation(new Pattern('(p)erson$'), '\1eople');
yield new Transformation(new Pattern('(m)an$'), '\1en');
yield new Transformation(new Pattern('(c)hild$'), '\1hildren');
yield new Transformation(new Pattern('(f)oot$'), '\1eet');
yield new Transformation(new Pattern('(buffal|her|potat|tomat|volcan)o$'), '\1\2oes');
yield new Transformation(new Pattern('(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$'), '\1i');
yield new Transformation(new Pattern('us$'), 'uses');
yield new Transformation(new Pattern('(alias)$'), '\1es');
yield new Transformation(new Pattern('(analys|ax|cris|test|thes)is$'), '\1es');
yield new Transformation(new Pattern('s$'), 's');
yield new Transformation(new Pattern('^$'), '');
yield new Transformation(new Pattern('$'), 's');
}
/**
* @return Substitution[]
*/
public static function getIrregular(): iterable
{
yield new Substitution(new Word('atlas'), new Word('atlases'));
yield new Substitution(new Word('axe'), new Word('axes'));
yield new Substitution(new Word('beef'), new Word('beefs'));
yield new Substitution(new Word('brother'), new Word('brothers'));
yield new Substitution(new Word('cafe'), new Word('cafes'));
yield new Substitution(new Word('chateau'), new Word('chateaux'));
yield new Substitution(new Word('niveau'), new Word('niveaux'));
yield new Substitution(new Word('child'), new Word('children'));
yield new Substitution(new Word('canvas'), new Word('canvases'));
yield new Substitution(new Word('cookie'), new Word('cookies'));
yield new Substitution(new Word('corpus'), new Word('corpuses'));
yield new Substitution(new Word('cow'), new Word('cows'));
yield new Substitution(new Word('criterion'), new Word('criteria'));
yield new Substitution(new Word('curriculum'), new Word('curricula'));
yield new Substitution(new Word('demo'), new Word('demos'));
yield new Substitution(new Word('domino'), new Word('dominoes'));
yield new Substitution(new Word('echo'), new Word('echoes'));
yield new Substitution(new Word('foot'), new Word('feet'));
yield new Substitution(new Word('fungus'), new Word('fungi'));
yield new Substitution(new Word('ganglion'), new Word('ganglions'));
yield new Substitution(new Word('gas'), new Word('gases'));
yield new Substitution(new Word('genie'), new Word('genies'));
yield new Substitution(new Word('genus'), new Word('genera'));
yield new Substitution(new Word('goose'), new Word('geese'));
yield new Substitution(new Word('graffito'), new Word('graffiti'));
yield new Substitution(new Word('hippopotamus'), new Word('hippopotami'));
yield new Substitution(new Word('hoof'), new Word('hoofs'));
yield new Substitution(new Word('human'), new Word('humans'));
yield new Substitution(new Word('iris'), new Word('irises'));
yield new Substitution(new Word('larva'), new Word('larvae'));
yield new Substitution(new Word('leaf'), new Word('leaves'));
yield new Substitution(new Word('lens'), new Word('lenses'));
yield new Substitution(new Word('loaf'), new Word('loaves'));
yield new Substitution(new Word('man'), new Word('men'));
yield new Substitution(new Word('medium'), new Word('media'));
yield new Substitution(new Word('memorandum'), new Word('memoranda'));
yield new Substitution(new Word('money'), new Word('monies'));
yield new Substitution(new Word('mongoose'), new Word('mongooses'));
yield new Substitution(new Word('motto'), new Word('mottoes'));
yield new Substitution(new Word('move'), new Word('moves'));
yield new Substitution(new Word('mythos'), new Word('mythoi'));
yield new Substitution(new Word('niche'), new Word('niches'));
yield new Substitution(new Word('nucleus'), new Word('nuclei'));
yield new Substitution(new Word('numen'), new Word('numina'));
yield new Substitution(new Word('occiput'), new Word('occiputs'));
yield new Substitution(new Word('octopus'), new Word('octopuses'));
yield new Substitution(new Word('opus'), new Word('opuses'));
yield new Substitution(new Word('ox'), new Word('oxen'));
yield new Substitution(new Word('passerby'), new Word('passersby'));
yield new Substitution(new Word('penis'), new Word('penises'));
yield new Substitution(new Word('person'), new Word('people'));
yield new Substitution(new Word('plateau'), new Word('plateaux'));
yield new Substitution(new Word('runner-up'), new Word('runners-up'));
yield new Substitution(new Word('safe'), new Word('safes'));
yield new Substitution(new Word('sex'), new Word('sexes'));
yield new Substitution(new Word('soliloquy'), new Word('soliloquies'));
yield new Substitution(new Word('son-in-law'), new Word('sons-in-law'));
yield new Substitution(new Word('syllabus'), new Word('syllabi'));
yield new Substitution(new Word('testis'), new Word('testes'));
yield new Substitution(new Word('thief'), new Word('thieves'));
yield new Substitution(new Word('tooth'), new Word('teeth'));
yield new Substitution(new Word('tornado'), new Word('tornadoes'));
yield new Substitution(new Word('trilby'), new Word('trilbys'));
yield new Substitution(new Word('turf'), new Word('turfs'));
yield new Substitution(new Word('valve'), new Word('valves'));
yield new Substitution(new Word('volcano'), new Word('volcanoes'));
yield new Substitution(new Word('abuse'), new Word('abuses'));
yield new Substitution(new Word('avalanche'), new Word('avalanches'));
yield new Substitution(new Word('cache'), new Word('caches'));
yield new Substitution(new Word('criterion'), new Word('criteria'));
yield new Substitution(new Word('curve'), new Word('curves'));
yield new Substitution(new Word('emphasis'), new Word('emphases'));
yield new Substitution(new Word('foe'), new Word('foes'));
yield new Substitution(new Word('grave'), new Word('graves'));
yield new Substitution(new Word('hoax'), new Word('hoaxes'));
yield new Substitution(new Word('medium'), new Word('media'));
yield new Substitution(new Word('neurosis'), new Word('neuroses'));
yield new Substitution(new Word('save'), new Word('saves'));
yield new Substitution(new Word('wave'), new Word('waves'));
yield new Substitution(new Word('oasis'), new Word('oases'));
yield new Substitution(new Word('valve'), new Word('valves'));
yield new Substitution(new Word('zombie'), new Word('zombies'));
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\English;
use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;
final class InflectorFactory extends GenericLanguageInflectorFactory
{
protected function getSingularRuleset(): Ruleset
{
return Rules::getSingularRuleset();
}
protected function getPluralRuleset(): Ruleset
{
return Rules::getPluralRuleset();
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\English;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;
final class Rules
{
public static function getSingularRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getSingular()),
new Patterns(...Uninflected::getSingular()),
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
);
}
public static function getPluralRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getPlural()),
new Patterns(...Uninflected::getPlural()),
new Substitutions(...Inflectible::getIrregular())
);
}
}

View File

@@ -0,0 +1,193 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\English;
use Doctrine\Inflector\Rules\Pattern;
final class Uninflected
{
/**
* @return Pattern[]
*/
public static function getSingular(): iterable
{
yield from self::getDefault();
yield new Pattern('.*ss');
yield new Pattern('clothes');
yield new Pattern('data');
yield new Pattern('fascia');
yield new Pattern('fuchsia');
yield new Pattern('galleria');
yield new Pattern('mafia');
yield new Pattern('militia');
yield new Pattern('pants');
yield new Pattern('petunia');
yield new Pattern('sepia');
yield new Pattern('trivia');
yield new Pattern('utopia');
}
/**
* @return Pattern[]
*/
public static function getPlural(): iterable
{
yield from self::getDefault();
yield new Pattern('people');
yield new Pattern('trivia');
yield new Pattern('\w+ware$');
yield new Pattern('media');
}
/**
* @return Pattern[]
*/
private static function getDefault(): iterable
{
yield new Pattern('\w+media');
yield new Pattern('advice');
yield new Pattern('aircraft');
yield new Pattern('amoyese');
yield new Pattern('art');
yield new Pattern('audio');
yield new Pattern('baggage');
yield new Pattern('bison');
yield new Pattern('borghese');
yield new Pattern('bream');
yield new Pattern('breeches');
yield new Pattern('britches');
yield new Pattern('buffalo');
yield new Pattern('butter');
yield new Pattern('cantus');
yield new Pattern('carp');
yield new Pattern('chassis');
yield new Pattern('clippers');
yield new Pattern('clothing');
yield new Pattern('coal');
yield new Pattern('cod');
yield new Pattern('coitus');
yield new Pattern('compensation');
yield new Pattern('congoese');
yield new Pattern('contretemps');
yield new Pattern('coreopsis');
yield new Pattern('corps');
yield new Pattern('cotton');
yield new Pattern('data');
yield new Pattern('debris');
yield new Pattern('deer');
yield new Pattern('diabetes');
yield new Pattern('djinn');
yield new Pattern('education');
yield new Pattern('eland');
yield new Pattern('elk');
yield new Pattern('emoji');
yield new Pattern('equipment');
yield new Pattern('evidence');
yield new Pattern('faroese');
yield new Pattern('feedback');
yield new Pattern('fish');
yield new Pattern('flounder');
yield new Pattern('flour');
yield new Pattern('foochowese');
yield new Pattern('food');
yield new Pattern('furniture');
yield new Pattern('gallows');
yield new Pattern('genevese');
yield new Pattern('genoese');
yield new Pattern('gilbertese');
yield new Pattern('gold');
yield new Pattern('headquarters');
yield new Pattern('herpes');
yield new Pattern('hijinks');
yield new Pattern('homework');
yield new Pattern('hottentotese');
yield new Pattern('impatience');
yield new Pattern('information');
yield new Pattern('innings');
yield new Pattern('jackanapes');
yield new Pattern('jeans');
yield new Pattern('jedi');
yield new Pattern('kiplingese');
yield new Pattern('knowledge');
yield new Pattern('kongoese');
yield new Pattern('leather');
yield new Pattern('love');
yield new Pattern('lucchese');
yield new Pattern('luggage');
yield new Pattern('mackerel');
yield new Pattern('Maltese');
yield new Pattern('management');
yield new Pattern('metadata');
yield new Pattern('mews');
yield new Pattern('money');
yield new Pattern('moose');
yield new Pattern('mumps');
yield new Pattern('music');
yield new Pattern('nankingese');
yield new Pattern('news');
yield new Pattern('nexus');
yield new Pattern('niasese');
yield new Pattern('nutrition');
yield new Pattern('offspring');
yield new Pattern('oil');
yield new Pattern('patience');
yield new Pattern('pekingese');
yield new Pattern('piedmontese');
yield new Pattern('pincers');
yield new Pattern('pistoiese');
yield new Pattern('plankton');
yield new Pattern('pliers');
yield new Pattern('pokemon');
yield new Pattern('police');
yield new Pattern('polish');
yield new Pattern('portuguese');
yield new Pattern('proceedings');
yield new Pattern('progress');
yield new Pattern('rabies');
yield new Pattern('rain');
yield new Pattern('research');
yield new Pattern('rhinoceros');
yield new Pattern('rice');
yield new Pattern('salmon');
yield new Pattern('sand');
yield new Pattern('sarawakese');
yield new Pattern('scissors');
yield new Pattern('sea[- ]bass');
yield new Pattern('series');
yield new Pattern('shavese');
yield new Pattern('shears');
yield new Pattern('sheep');
yield new Pattern('siemens');
yield new Pattern('silk');
yield new Pattern('sms');
yield new Pattern('soap');
yield new Pattern('social media');
yield new Pattern('spam');
yield new Pattern('species');
yield new Pattern('staff');
yield new Pattern('sugar');
yield new Pattern('swine');
yield new Pattern('talent');
yield new Pattern('toothpaste');
yield new Pattern('traffic');
yield new Pattern('travel');
yield new Pattern('trousers');
yield new Pattern('trout');
yield new Pattern('tuna');
yield new Pattern('us');
yield new Pattern('vermontese');
yield new Pattern('vinegar');
yield new Pattern('weather');
yield new Pattern('wenchowese');
yield new Pattern('wheat');
yield new Pattern('whiting');
yield new Pattern('wildebeest');
yield new Pattern('wood');
yield new Pattern('wool');
yield new Pattern('yengeese');
}
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\French;
use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;
class Inflectible
{
/**
* @return Transformation[]
*/
public static function getSingular(): iterable
{
yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)aux$/'), '\1ail');
yield new Transformation(new Pattern('/ails$/'), 'ail');
yield new Transformation(new Pattern('/(journ|chev)aux$/'), '\1al');
yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)x$/'), '\1');
yield new Transformation(new Pattern('/s$/'), '');
}
/**
* @return Transformation[]
*/
public static function getPlural(): iterable
{
yield new Transformation(new Pattern('/(s|x|z)$/'), '\1');
yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)ail$/'), '\1aux');
yield new Transformation(new Pattern('/ail$/'), 'ails');
yield new Transformation(new Pattern('/(chacal|carnaval|festival|récital)$/'), '\1s');
yield new Transformation(new Pattern('/al$/'), 'aux');
yield new Transformation(new Pattern('/(bleu|émeu|landau|pneu|sarrau)$/'), '\1s');
yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|lieu|pou|au|eu|eau)$/'), '\1x');
yield new Transformation(new Pattern('/$/'), 's');
}
/**
* @return Substitution[]
*/
public static function getIrregular(): iterable
{
yield new Substitution(new Word('monsieur'), new Word('messieurs'));
yield new Substitution(new Word('madame'), new Word('mesdames'));
yield new Substitution(new Word('mademoiselle'), new Word('mesdemoiselles'));
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\French;
use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;
final class InflectorFactory extends GenericLanguageInflectorFactory
{
protected function getSingularRuleset(): Ruleset
{
return Rules::getSingularRuleset();
}
protected function getPluralRuleset(): Ruleset
{
return Rules::getPluralRuleset();
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\French;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;
final class Rules
{
public static function getSingularRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getSingular()),
new Patterns(...Uninflected::getSingular()),
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
);
}
public static function getPluralRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getPlural()),
new Patterns(...Uninflected::getPlural()),
new Substitutions(...Inflectible::getIrregular())
);
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\French;
use Doctrine\Inflector\Rules\Pattern;
final class Uninflected
{
/**
* @return Pattern[]
*/
public static function getSingular(): iterable
{
yield from self::getDefault();
}
/**
* @return Pattern[]
*/
public static function getPlural(): iterable
{
yield from self::getDefault();
}
/**
* @return Pattern[]
*/
private static function getDefault(): iterable
{
yield new Pattern('');
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\NorwegianBokmal;
use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;
class Inflectible
{
/**
* @return Transformation[]
*/
public static function getSingular(): iterable
{
yield new Transformation(new Pattern('/re$/i'), 'r');
yield new Transformation(new Pattern('/er$/i'), '');
}
/**
* @return Transformation[]
*/
public static function getPlural(): iterable
{
yield new Transformation(new Pattern('/e$/i'), 'er');
yield new Transformation(new Pattern('/r$/i'), 're');
yield new Transformation(new Pattern('/$/'), 'er');
}
/**
* @return Substitution[]
*/
public static function getIrregular(): iterable
{
yield new Substitution(new Word('konto'), new Word('konti'));
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\NorwegianBokmal;
use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;
final class InflectorFactory extends GenericLanguageInflectorFactory
{
protected function getSingularRuleset(): Ruleset
{
return Rules::getSingularRuleset();
}
protected function getPluralRuleset(): Ruleset
{
return Rules::getPluralRuleset();
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\NorwegianBokmal;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;
final class Rules
{
public static function getSingularRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getSingular()),
new Patterns(...Uninflected::getSingular()),
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
);
}
public static function getPluralRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getPlural()),
new Patterns(...Uninflected::getPlural()),
new Substitutions(...Inflectible::getIrregular())
);
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\NorwegianBokmal;
use Doctrine\Inflector\Rules\Pattern;
final class Uninflected
{
/**
* @return Pattern[]
*/
public static function getSingular(): iterable
{
yield from self::getDefault();
}
/**
* @return Pattern[]
*/
public static function getPlural(): iterable
{
yield from self::getDefault();
}
/**
* @return Pattern[]
*/
private static function getDefault(): iterable
{
yield new Pattern('barn');
yield new Pattern('fjell');
yield new Pattern('hus');
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules;
use function preg_match;
final class Pattern
{
/** @var string */
private $pattern;
/** @var string */
private $regex;
public function __construct(string $pattern)
{
$this->pattern = $pattern;
if (isset($this->pattern[0]) && $this->pattern[0] === '/') {
$this->regex = $this->pattern;
} else {
$this->regex = '/' . $this->pattern . '/i';
}
}
public function getPattern(): string
{
return $this->pattern;
}
public function getRegex(): string
{
return $this->regex;
}
public function matches(string $word): bool
{
return preg_match($this->getRegex(), $word) === 1;
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules;
use function array_map;
use function implode;
use function preg_match;
class Patterns
{
/** @var Pattern[] */
private $patterns;
/** @var string */
private $regex;
public function __construct(Pattern ...$patterns)
{
$this->patterns = $patterns;
$patterns = array_map(static function (Pattern $pattern): string {
return $pattern->getPattern();
}, $this->patterns);
$this->regex = '/^(?:' . implode('|', $patterns) . ')$/i';
}
public function matches(string $word): bool
{
return preg_match($this->regex, $word, $regs) === 1;
}
}

View File

@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Portuguese;
use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;
class Inflectible
{
/**
* @return Transformation[]
*/
public static function getSingular(): iterable
{
yield new Transformation(new Pattern('/^(g|)ases$/i'), '\1ás');
yield new Transformation(new Pattern('/(japon|escoc|ingl|dinamarqu|fregu|portugu)eses$/i'), '\1ês');
yield new Transformation(new Pattern('/(ae|ao|oe)s$/'), 'ao');
yield new Transformation(new Pattern('/(ãe|ão|õe)s$/'), 'ão');
yield new Transformation(new Pattern('/^(.*[^s]s)es$/i'), '\1');
yield new Transformation(new Pattern('/sses$/i'), 'sse');
yield new Transformation(new Pattern('/ns$/i'), 'm');
yield new Transformation(new Pattern('/(r|t|f|v)is$/i'), '\1il');
yield new Transformation(new Pattern('/uis$/i'), 'ul');
yield new Transformation(new Pattern('/ois$/i'), 'ol');
yield new Transformation(new Pattern('/eis$/i'), 'ei');
yield new Transformation(new Pattern('/éis$/i'), 'el');
yield new Transformation(new Pattern('/([^p])ais$/i'), '\1al');
yield new Transformation(new Pattern('/(r|z)es$/i'), '\1');
yield new Transformation(new Pattern('/^(á|gá)s$/i'), '\1s');
yield new Transformation(new Pattern('/([^ê])s$/i'), '\1');
}
/**
* @return Transformation[]
*/
public static function getPlural(): iterable
{
yield new Transformation(new Pattern('/^(alem|c|p)ao$/i'), '\1aes');
yield new Transformation(new Pattern('/^(irm|m)ao$/i'), '\1aos');
yield new Transformation(new Pattern('/ao$/i'), 'oes');
yield new Transformation(new Pattern('/^(alem|c|p)ão$/i'), '\1ães');
yield new Transformation(new Pattern('/^(irm|m)ão$/i'), '\1ãos');
yield new Transformation(new Pattern('/ão$/i'), 'ões');
yield new Transformation(new Pattern('/^(|g)ás$/i'), '\1ases');
yield new Transformation(new Pattern('/^(japon|escoc|ingl|dinamarqu|fregu|portugu)ês$/i'), '\1eses');
yield new Transformation(new Pattern('/m$/i'), 'ns');
yield new Transformation(new Pattern('/([^aeou])il$/i'), '\1is');
yield new Transformation(new Pattern('/ul$/i'), 'uis');
yield new Transformation(new Pattern('/ol$/i'), 'ois');
yield new Transformation(new Pattern('/el$/i'), 'eis');
yield new Transformation(new Pattern('/al$/i'), 'ais');
yield new Transformation(new Pattern('/(z|r)$/i'), '\1es');
yield new Transformation(new Pattern('/(s)$/i'), '\1');
yield new Transformation(new Pattern('/$/'), 's');
}
/**
* @return Substitution[]
*/
public static function getIrregular(): iterable
{
yield new Substitution(new Word('abdomen'), new Word('abdomens'));
yield new Substitution(new Word('alemão'), new Word('alemães'));
yield new Substitution(new Word('artesã'), new Word('artesãos'));
yield new Substitution(new Word('álcool'), new Word('álcoois'));
yield new Substitution(new Word('árvore'), new Word('árvores'));
yield new Substitution(new Word('bencão'), new Word('bencãos'));
yield new Substitution(new Word('cão'), new Word('cães'));
yield new Substitution(new Word('campus'), new Word('campi'));
yield new Substitution(new Word('cadáver'), new Word('cadáveres'));
yield new Substitution(new Word('capelão'), new Word('capelães'));
yield new Substitution(new Word('capitão'), new Word('capitães'));
yield new Substitution(new Word('chão'), new Word('chãos'));
yield new Substitution(new Word('charlatão'), new Word('charlatães'));
yield new Substitution(new Word('cidadão'), new Word('cidadãos'));
yield new Substitution(new Word('consul'), new Word('consules'));
yield new Substitution(new Word('cristão'), new Word('cristãos'));
yield new Substitution(new Word('difícil'), new Word('difíceis'));
yield new Substitution(new Word('email'), new Word('emails'));
yield new Substitution(new Word('escrivão'), new Word('escrivães'));
yield new Substitution(new Word('fóssil'), new Word('fósseis'));
yield new Substitution(new Word('gás'), new Word('gases'));
yield new Substitution(new Word('germens'), new Word('germen'));
yield new Substitution(new Word('grão'), new Word('grãos'));
yield new Substitution(new Word('hífen'), new Word('hífens'));
yield new Substitution(new Word('irmão'), new Word('irmãos'));
yield new Substitution(new Word('liquens'), new Word('liquen'));
yield new Substitution(new Word('mal'), new Word('males'));
yield new Substitution(new Word('mão'), new Word('mãos'));
yield new Substitution(new Word('orfão'), new Word('orfãos'));
yield new Substitution(new Word('país'), new Word('países'));
yield new Substitution(new Word('pai'), new Word('pais'));
yield new Substitution(new Word('pão'), new Word('pães'));
yield new Substitution(new Word('projétil'), new Word('projéteis'));
yield new Substitution(new Word('réptil'), new Word('répteis'));
yield new Substitution(new Word('sacristão'), new Word('sacristães'));
yield new Substitution(new Word('sotão'), new Word('sotãos'));
yield new Substitution(new Word('tabelião'), new Word('tabeliães'));
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Portuguese;
use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;
final class InflectorFactory extends GenericLanguageInflectorFactory
{
protected function getSingularRuleset(): Ruleset
{
return Rules::getSingularRuleset();
}
protected function getPluralRuleset(): Ruleset
{
return Rules::getPluralRuleset();
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Portuguese;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;
final class Rules
{
public static function getSingularRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getSingular()),
new Patterns(...Uninflected::getSingular()),
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
);
}
public static function getPluralRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getPlural()),
new Patterns(...Uninflected::getPlural()),
new Substitutions(...Inflectible::getIrregular())
);
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Portuguese;
use Doctrine\Inflector\Rules\Pattern;
final class Uninflected
{
/**
* @return Pattern[]
*/
public static function getSingular(): iterable
{
yield from self::getDefault();
}
/**
* @return Pattern[]
*/
public static function getPlural(): iterable
{
yield from self::getDefault();
}
/**
* @return Pattern[]
*/
private static function getDefault(): iterable
{
yield new Pattern('tórax');
yield new Pattern('tênis');
yield new Pattern('ônibus');
yield new Pattern('lápis');
yield new Pattern('fênix');
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules;
class Ruleset
{
/** @var Transformations */
private $regular;
/** @var Patterns */
private $uninflected;
/** @var Substitutions */
private $irregular;
public function __construct(Transformations $regular, Patterns $uninflected, Substitutions $irregular)
{
$this->regular = $regular;
$this->uninflected = $uninflected;
$this->irregular = $irregular;
}
public function getRegular(): Transformations
{
return $this->regular;
}
public function getUninflected(): Patterns
{
return $this->uninflected;
}
public function getIrregular(): Substitutions
{
return $this->irregular;
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Spanish;
use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;
class Inflectible
{
/**
* @return Transformation[]
*/
public static function getSingular(): iterable
{
yield new Transformation(new Pattern('/ereses$/'), 'erés');
yield new Transformation(new Pattern('/iones$/'), 'ión');
yield new Transformation(new Pattern('/ces$/'), 'z');
yield new Transformation(new Pattern('/es$/'), '');
yield new Transformation(new Pattern('/s$/'), '');
}
/**
* @return Transformation[]
*/
public static function getPlural(): iterable
{
yield new Transformation(new Pattern('/ú([sn])$/i'), 'u\1es');
yield new Transformation(new Pattern('/ó([sn])$/i'), 'o\1es');
yield new Transformation(new Pattern('/í([sn])$/i'), 'i\1es');
yield new Transformation(new Pattern('/é([sn])$/i'), 'e\1es');
yield new Transformation(new Pattern('/á([sn])$/i'), 'a\1es');
yield new Transformation(new Pattern('/z$/i'), 'ces');
yield new Transformation(new Pattern('/([aeiou]s)$/i'), '\1');
yield new Transformation(new Pattern('/([^aeéiou])$/i'), '\1es');
yield new Transformation(new Pattern('/$/'), 's');
}
/**
* @return Substitution[]
*/
public static function getIrregular(): iterable
{
yield new Substitution(new Word('el'), new Word('los'));
yield new Substitution(new Word('papá'), new Word('papás'));
yield new Substitution(new Word('mamá'), new Word('mamás'));
yield new Substitution(new Word('sofá'), new Word('sofás'));
yield new Substitution(new Word('mes'), new Word('meses'));
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Spanish;
use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;
final class InflectorFactory extends GenericLanguageInflectorFactory
{
protected function getSingularRuleset(): Ruleset
{
return Rules::getSingularRuleset();
}
protected function getPluralRuleset(): Ruleset
{
return Rules::getPluralRuleset();
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Spanish;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;
final class Rules
{
public static function getSingularRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getSingular()),
new Patterns(...Uninflected::getSingular()),
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
);
}
public static function getPluralRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getPlural()),
new Patterns(...Uninflected::getPlural()),
new Substitutions(...Inflectible::getIrregular())
);
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Spanish;
use Doctrine\Inflector\Rules\Pattern;
final class Uninflected
{
/**
* @return Pattern[]
*/
public static function getSingular(): iterable
{
yield from self::getDefault();
}
/**
* @return Pattern[]
*/
public static function getPlural(): iterable
{
yield from self::getDefault();
}
/**
* @return Pattern[]
*/
private static function getDefault(): iterable
{
yield new Pattern('lunes');
yield new Pattern('rompecabezas');
yield new Pattern('crisis');
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules;
final class Substitution
{
/** @var Word */
private $from;
/** @var Word */
private $to;
public function __construct(Word $from, Word $to)
{
$this->from = $from;
$this->to = $to;
}
public function getFrom(): Word
{
return $this->from;
}
public function getTo(): Word
{
return $this->to;
}
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules;
use Doctrine\Inflector\WordInflector;
use function strtolower;
use function strtoupper;
use function substr;
class Substitutions implements WordInflector
{
/** @var Substitution[] */
private $substitutions;
public function __construct(Substitution ...$substitutions)
{
foreach ($substitutions as $substitution) {
$this->substitutions[$substitution->getFrom()->getWord()] = $substitution;
}
}
public function getFlippedSubstitutions(): Substitutions
{
$substitutions = [];
foreach ($this->substitutions as $substitution) {
$substitutions[] = new Substitution(
$substitution->getTo(),
$substitution->getFrom()
);
}
return new Substitutions(...$substitutions);
}
public function inflect(string $word): string
{
$lowerWord = strtolower($word);
if (isset($this->substitutions[$lowerWord])) {
$firstLetterUppercase = $lowerWord[0] !== $word[0];
$toWord = $this->substitutions[$lowerWord]->getTo()->getWord();
if ($firstLetterUppercase) {
return strtoupper($toWord[0]) . substr($toWord, 1);
}
return $toWord;
}
return $word;
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules;
use Doctrine\Inflector\WordInflector;
use function preg_replace;
final class Transformation implements WordInflector
{
/** @var Pattern */
private $pattern;
/** @var string */
private $replacement;
public function __construct(Pattern $pattern, string $replacement)
{
$this->pattern = $pattern;
$this->replacement = $replacement;
}
public function getPattern(): Pattern
{
return $this->pattern;
}
public function getReplacement(): string
{
return $this->replacement;
}
public function inflect(string $word): string
{
return (string) preg_replace($this->pattern->getRegex(), $this->replacement, $word);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules;
use Doctrine\Inflector\WordInflector;
class Transformations implements WordInflector
{
/** @var Transformation[] */
private $transformations;
public function __construct(Transformation ...$transformations)
{
$this->transformations = $transformations;
}
public function inflect(string $word): string
{
foreach ($this->transformations as $transformation) {
if ($transformation->getPattern()->matches($word)) {
return $transformation->inflect($word);
}
}
return $word;
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Turkish;
use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;
class Inflectible
{
/**
* @return Transformation[]
*/
public static function getSingular(): iterable
{
yield new Transformation(new Pattern('/l[ae]r$/i'), '');
}
/**
* @return Transformation[]
*/
public static function getPlural(): iterable
{
yield new Transformation(new Pattern('/([eöiü][^aoıueöiü]{0,6})$/u'), '\1ler');
yield new Transformation(new Pattern('/([aoıu][^aoıueöiü]{0,6})$/u'), '\1lar');
}
/**
* @return Substitution[]
*/
public static function getIrregular(): iterable
{
yield new Substitution(new Word('ben'), new Word('biz'));
yield new Substitution(new Word('sen'), new Word('siz'));
yield new Substitution(new Word('o'), new Word('onlar'));
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Turkish;
use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;
final class InflectorFactory extends GenericLanguageInflectorFactory
{
protected function getSingularRuleset(): Ruleset
{
return Rules::getSingularRuleset();
}
protected function getPluralRuleset(): Ruleset
{
return Rules::getPluralRuleset();
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Turkish;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;
final class Rules
{
public static function getSingularRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getSingular()),
new Patterns(...Uninflected::getSingular()),
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
);
}
public static function getPluralRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getPlural()),
new Patterns(...Uninflected::getPlural()),
new Substitutions(...Inflectible::getIrregular())
);
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Turkish;
use Doctrine\Inflector\Rules\Pattern;
final class Uninflected
{
/**
* @return Pattern[]
*/
public static function getSingular(): iterable
{
yield from self::getDefault();
}
/**
* @return Pattern[]
*/
public static function getPlural(): iterable
{
yield from self::getDefault();
}
/**
* @return Pattern[]
*/
private static function getDefault(): iterable
{
yield new Pattern('lunes');
yield new Pattern('rompecabezas');
yield new Pattern('crisis');
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules;
class Word
{
/** @var string */
private $word;
public function __construct(string $word)
{
$this->word = $word;
}
public function getWord(): string
{
return $this->word;
}
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
use Doctrine\Inflector\Rules\Ruleset;
use function array_merge;
/**
* Inflects based on multiple rulesets.
*
* Rules:
* - If the word matches any uninflected word pattern, it is not inflected
* - The first ruleset that returns a different value for an irregular word wins
* - The first ruleset that returns a different value for a regular word wins
* - If none of the above match, the word is left as-is
*/
class RulesetInflector implements WordInflector
{
/** @var Ruleset[] */
private $rulesets;
public function __construct(Ruleset $ruleset, Ruleset ...$rulesets)
{
$this->rulesets = array_merge([$ruleset], $rulesets);
}
public function inflect(string $word): string
{
if ($word === '') {
return '';
}
foreach ($this->rulesets as $ruleset) {
if ($ruleset->getUninflected()->matches($word)) {
return $word;
}
$inflected = $ruleset->getIrregular()->inflect($word);
if ($inflected !== $word) {
return $inflected;
}
$inflected = $ruleset->getRegular()->inflect($word);
if ($inflected !== $word) {
return $inflected;
}
}
return $word;
}
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
interface WordInflector
{
public function inflect(string $word): string;
}

View File

@@ -0,0 +1,13 @@
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon
- vendor/phpstan/phpstan-strict-rules/rules.neon
parameters:
level: 7
paths:
- lib
- tests
excludes_analyse:
- %rootDir%/../../../tests/Doctrine/Tests/Common/*

15
vendor/doctrine/inflector/psalm.xml vendored Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<psalm
errorLevel="7"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="lib/Doctrine/Inflector" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

View File

@@ -0,0 +1,47 @@
{
"active": true,
"name": "Instantiator",
"slug": "instantiator",
"docsSlug": "doctrine-instantiator",
"codePath": "/src",
"versions": [
{
"name": "1.5",
"branchName": "1.5.x",
"slug": "latest",
"upcoming": true
},
{
"name": "1.4",
"branchName": "1.4.x",
"slug": "1.4",
"aliases": [
"current",
"stable"
],
"maintained": true,
"current": true
},
{
"name": "1.3",
"branchName": "1.3.x",
"slug": "1.3",
"maintained": false
},
{
"name": "1.2",
"branchName": "1.2.x",
"slug": "1.2"
},
{
"name": "1.1",
"branchName": "1.1.x",
"slug": "1.1"
},
{
"name": "1.0",
"branchName": "1.0.x",
"slug": "1.0"
}
]
}

View File

@@ -0,0 +1,35 @@
# Contributing
* Follow the [Doctrine Coding Standard](https://github.com/doctrine/coding-standard)
* The project will follow strict [object calisthenics](http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php)
* Any contribution must provide tests for additional introduced conditions
* Any un-confirmed issue needs a failing test case before being accepted
* Pull requests must be sent from a new hotfix/feature branch, not from `master`.
## Installation
To install the project and run the tests, you need to clone it first:
```sh
$ git clone git://github.com/doctrine/instantiator.git
```
You will then need to run a composer installation:
```sh
$ cd Instantiator
$ curl -s https://getcomposer.org/installer | php
$ php composer.phar update
```
## Testing
The PHPUnit version to be used is the one installed as a dev- dependency via composer:
```sh
$ ./vendor/bin/phpunit
```
Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement
won't be merged.

19
vendor/doctrine/instantiator/LICENSE vendored Normal file
View File

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

38
vendor/doctrine/instantiator/README.md vendored Normal file
View File

@@ -0,0 +1,38 @@
# Instantiator
This library provides a way of avoiding usage of constructors when instantiating PHP classes.
[![Build Status](https://travis-ci.org/doctrine/instantiator.svg?branch=master)](https://travis-ci.org/doctrine/instantiator)
[![Code Coverage](https://codecov.io/gh/doctrine/instantiator/branch/master/graph/badge.svg)](https://codecov.io/gh/doctrine/instantiator/branch/master)
[![Dependency Status](https://www.versioneye.com/package/php--doctrine--instantiator/badge.svg)](https://www.versioneye.com/package/php--doctrine--instantiator)
[![Latest Stable Version](https://poser.pugx.org/doctrine/instantiator/v/stable.png)](https://packagist.org/packages/doctrine/instantiator)
[![Latest Unstable Version](https://poser.pugx.org/doctrine/instantiator/v/unstable.png)](https://packagist.org/packages/doctrine/instantiator)
## Installation
The suggested installation method is via [composer](https://getcomposer.org/):
```sh
php composer.phar require "doctrine/instantiator:~1.0.3"
```
## Usage
The instantiator is able to create new instances of any class without using the constructor or any API of the class
itself:
```php
$instantiator = new \Doctrine\Instantiator\Instantiator();
$instance = $instantiator->instantiate(\My\ClassName\Here::class);
```
## Contributing
Please read the [CONTRIBUTING.md](CONTRIBUTING.md) contents if you wish to help out!
## Credits
This library was migrated from [ocramius/instantiator](https://github.com/Ocramius/Instantiator), which
has been donated to the doctrine organization, and which is now deprecated in favour of this package.

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