Update illuminate/database

This commit is contained in:
Floorb 2022-03-14 16:22:30 -04:00
parent 863abeeeaf
commit ba0f8a85f4
980 changed files with 13376 additions and 6966 deletions

View file

@ -18,7 +18,7 @@
"ext-mbstring": "*",
"scrivo/highlight.php": "v9.18.1.7",
"erusev/parsedown": "^1.7",
"illuminate/database": "^8.56",
"illuminate/database": "^9.4",
"ext-redis": "*"
},
"autoload": {

729
composer.lock generated

File diff suppressed because it is too large Load diff

1
vendor/bin/carbon vendored
View file

@ -1 +0,0 @@
../nesbot/carbon/bin/carbon

117
vendor/bin/carbon vendored Executable file
View file

@ -0,0 +1,117 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../nesbot/carbon/bin/carbon)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/nesbot/carbon/bin/carbon');
exit(0);
}
}
include __DIR__ . '/..'.'/nesbot/carbon/bin/carbon';

View file

@ -42,30 +42,75 @@ namespace Composer\Autoload;
*/
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)) {
@ -75,28 +120,47 @@ class ClassLoader
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 array $classMap Class to filename map
* @param string[] $classMap Class to filename map
* @psalm-param array<string, string> $classMap
*
* @return void
*/
public function addClassMap(array $classMap)
{
@ -112,8 +176,10 @@ class ClassLoader
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param 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)
{
@ -157,10 +223,12 @@ class ClassLoader
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param 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)
{
@ -205,7 +273,9 @@ class ClassLoader
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
* @param string[]|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
@ -221,9 +291,11 @@ class ClassLoader
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param string[]|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
@ -243,6 +315,8 @@ class ClassLoader
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
@ -265,6 +339,8 @@ class ClassLoader
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
@ -285,6 +361,8 @@ class ClassLoader
* 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)
{
@ -305,6 +383,8 @@ class ClassLoader
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
@ -324,6 +404,8 @@ class ClassLoader
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
@ -403,6 +485,11 @@ class ClassLoader
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
@ -474,6 +561,10 @@ class ClassLoader
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
* @private
*/
function includeFile($file)
{

View file

@ -20,12 +20,25 @@ use Composer\Semver\VersionParser;
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require it's presence, you can require `composer-runtime-api ^2.0`
* 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();
/**
@ -228,7 +241,7 @@ class InstalledVersions
/**
* @return array
* @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}
* @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()
{
@ -242,7 +255,7 @@ class InstalledVersions
*
* @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}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}
* @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()
{
@ -265,7 +278,7 @@ class InstalledVersions
* 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}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}>
* @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()
{
@ -288,7 +301,7 @@ class InstalledVersions
* @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}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>} $data
* @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)
{
@ -298,7 +311,7 @@ class InstalledVersions
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}>
* @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()
{

View file

@ -8,8 +8,8 @@ $baseDir = dirname($vendorDir);
return array(
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',

View file

@ -7,15 +7,13 @@ $baseDir = dirname($vendorDir);
return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'60799491728b879e74601d83e38b2cad' => $vendorDir . '/illuminate/collections/helpers.php',
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'60799491728b879e74601d83e38b2cad' => $vendorDir . '/illuminate/collections/helpers.php',
'72579e7bd17821bb1321b87411366eae' => $vendorDir . '/illuminate/support/helpers.php',
'b6ec61354e97f32c0ae683041c78392a' => $vendorDir . '/scrivo/highlight.php/HighlightUtilities/functions.php',
);

View file

@ -8,7 +8,6 @@ $baseDir = dirname($vendorDir);
return array(
'voku\\' => array($vendorDir . '/voku/portable-ascii/src/voku'),
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'),
@ -21,7 +20,7 @@ return array(
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'PonePaste\\' => array($baseDir . '/includes'),
'Illuminate\\Support\\' => array($vendorDir . '/illuminate/collections', $vendorDir . '/illuminate/macroable', $vendorDir . '/illuminate/support'),
'Illuminate\\Support\\' => array($vendorDir . '/illuminate/conditionable', $vendorDir . '/illuminate/macroable', $vendorDir . '/illuminate/collections', $vendorDir . '/illuminate/support'),
'Illuminate\\Database\\' => array($vendorDir . '/illuminate/database'),
'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'),
'Illuminate\\Container\\' => array($vendorDir . '/illuminate/container'),

View file

@ -65,11 +65,16 @@ class ComposerAutoloaderInit5bf95489f4eff2c10ec062bf7ba377da
}
}
/**
* @param string $fileIdentifier
* @param string $file
* @return void
*/
function composerRequire5bf95489f4eff2c10ec062bf7ba377da($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}

View file

@ -8,15 +8,13 @@ class ComposerStaticInit5bf95489f4eff2c10ec062bf7ba377da
{
public static $files = array (
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
'60799491728b879e74601d83e38b2cad' => __DIR__ . '/..' . '/illuminate/collections/helpers.php',
'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'60799491728b879e74601d83e38b2cad' => __DIR__ . '/..' . '/illuminate/collections/helpers.php',
'72579e7bd17821bb1321b87411366eae' => __DIR__ . '/..' . '/illuminate/support/helpers.php',
'b6ec61354e97f32c0ae683041c78392a' => __DIR__ . '/..' . '/scrivo/highlight.php/HighlightUtilities/functions.php',
);
@ -29,7 +27,6 @@ class ComposerStaticInit5bf95489f4eff2c10ec062bf7ba377da
'S' =>
array (
'Symfony\\Polyfill\\Php80\\' => 23,
'Symfony\\Polyfill\\Php73\\' => 23,
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31,
@ -72,10 +69,6 @@ class ComposerStaticInit5bf95489f4eff2c10ec062bf7ba377da
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
),
'Symfony\\Polyfill\\Php73\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php73',
),
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
@ -126,9 +119,10 @@ class ComposerStaticInit5bf95489f4eff2c10ec062bf7ba377da
),
'Illuminate\\Support\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/collections',
0 => __DIR__ . '/..' . '/illuminate/conditionable',
1 => __DIR__ . '/..' . '/illuminate/macroable',
2 => __DIR__ . '/..' . '/illuminate/support',
2 => __DIR__ . '/..' . '/illuminate/collections',
3 => __DIR__ . '/..' . '/illuminate/support',
),
'Illuminate\\Database\\' =>
array (
@ -176,8 +170,8 @@ class ComposerStaticInit5bf95489f4eff2c10ec062bf7ba377da
public static $classMap = array (
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '070545e294d5191c315f4691080b63cd083955b1',
'reference' => '3c0fc75d296bbf2703fa00ff1a7ece4cc9c2164e',
'name' => 'aftercase/ponepaste',
'dev' => true,
),
@ -16,16 +16,16 @@
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '070545e294d5191c315f4691080b63cd083955b1',
'reference' => '3c0fc75d296bbf2703fa00ff1a7ece4cc9c2164e',
'dev_requirement' => false,
),
'doctrine/inflector' => array(
'pretty_version' => '2.0.3',
'version' => '2.0.3.0',
'pretty_version' => '2.0.4',
'version' => '2.0.4.0',
'type' => 'library',
'install_path' => __DIR__ . '/../doctrine/inflector',
'aliases' => array(),
'reference' => '9cf661f4eb38f7c881cac67c75ea9b00bf97b210',
'reference' => '8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89',
'dev_requirement' => false,
),
'erusev/parsedown' => array(
@ -38,96 +38,105 @@
'dev_requirement' => false,
),
'illuminate/collections' => array(
'pretty_version' => 'v8.57.0',
'version' => '8.57.0.0',
'pretty_version' => 'v9.4.1',
'version' => '9.4.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../illuminate/collections',
'aliases' => array(),
'reference' => '673d71d9db2827b04c096c4fe9739edddea14ac4',
'reference' => '22c4bb17f4e6c6fb470b5957e8232b1b5baf76b0',
'dev_requirement' => false,
),
'illuminate/conditionable' => array(
'pretty_version' => 'v9.4.1',
'version' => '9.4.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../illuminate/conditionable',
'aliases' => array(),
'reference' => '56b4ba1166c264064bf63896f498a2bee320d16a',
'dev_requirement' => false,
),
'illuminate/container' => array(
'pretty_version' => 'v8.57.0',
'version' => '8.57.0.0',
'pretty_version' => 'v9.4.1',
'version' => '9.4.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../illuminate/container',
'aliases' => array(),
'reference' => 'ecb645e1838cdee62eebd07ded490b1de5505f35',
'reference' => '66f9049b19fb34e74134c6eeff92a442cee068e5',
'dev_requirement' => false,
),
'illuminate/contracts' => array(
'pretty_version' => 'v8.57.0',
'version' => '8.57.0.0',
'pretty_version' => 'v9.4.1',
'version' => '9.4.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../illuminate/contracts',
'aliases' => array(),
'reference' => '7354badf7b57eae805a56d1163fb023e0f58a639',
'reference' => 'ce68106c575410c71f92ac1c91c5d95c561033bc',
'dev_requirement' => false,
),
'illuminate/database' => array(
'pretty_version' => 'v8.57.0',
'version' => '8.57.0.0',
'pretty_version' => 'v9.4.1',
'version' => '9.4.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../illuminate/database',
'aliases' => array(),
'reference' => '828d1cd4ee824c405f6edc52ab3d333b549be4ee',
'reference' => '0fffd6ba91eb58330cbf7331c77ea38c2a16b5d9',
'dev_requirement' => false,
),
'illuminate/macroable' => array(
'pretty_version' => 'v8.57.0',
'version' => '8.57.0.0',
'pretty_version' => 'v9.4.1',
'version' => '9.4.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../illuminate/macroable',
'aliases' => array(),
'reference' => '300aa13c086f25116b5f3cde3ca54ff5c822fb05',
'reference' => '25a2c6dac2b7541ecbadef952702e84ae15f5354',
'dev_requirement' => false,
),
'illuminate/support' => array(
'pretty_version' => 'v8.57.0',
'version' => '8.57.0.0',
'pretty_version' => 'v9.4.1',
'version' => '9.4.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../illuminate/support',
'aliases' => array(),
'reference' => '5e8f059a5d1f298324e847822b997778a3472128',
'reference' => '568ed7a21a75e0bd9ca641b6c4a626872ee26d6f',
'dev_requirement' => false,
),
'nesbot/carbon' => array(
'pretty_version' => '2.52.0',
'version' => '2.52.0.0',
'pretty_version' => '2.57.0',
'version' => '2.57.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../nesbot/carbon',
'aliases' => array(),
'reference' => '369c0e2737c56a0f39c946dd261855255a6fccbe',
'reference' => '4a54375c21eea4811dbd1149fe6b246517554e78',
'dev_requirement' => false,
),
'psr/container' => array(
'pretty_version' => '1.1.1',
'version' => '1.1.1.0',
'pretty_version' => '2.0.2',
'version' => '2.0.2.0',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/container',
'aliases' => array(),
'reference' => '8622567409010282b7aeebe4bb841fe98b58dcaf',
'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963',
'dev_requirement' => false,
),
'psr/container-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
0 => '1.1|2.0',
),
),
'psr/log-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0|2.0',
0 => '1.0|2.0|3.0',
),
),
'psr/simple-cache' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'pretty_version' => '3.0.0',
'version' => '3.0.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/simple-cache',
'aliases' => array(),
'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b',
'reference' => '764e0b3939f5ca87cb904f570ef9be2d78a07865',
'dev_requirement' => false,
),
'scrivo/highlight.php' => array(
@ -140,44 +149,35 @@
'dev_requirement' => false,
),
'symfony/console' => array(
'pretty_version' => 'v5.3.6',
'version' => '5.3.6.0',
'pretty_version' => 'v6.0.5',
'version' => '6.0.5.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/console',
'aliases' => array(),
'reference' => '51b71afd6d2dc8f5063199357b9880cea8d8bfe2',
'dev_requirement' => false,
),
'symfony/deprecation-contracts' => array(
'pretty_version' => 'v2.4.0',
'version' => '2.4.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
'aliases' => array(),
'reference' => '5f38c8804a9e97d23e0c8d63341088cd8a22d627',
'reference' => '3bebf4108b9e07492a2a4057d207aa5a77d146b1',
'dev_requirement' => false,
),
'symfony/polyfill-ctype' => array(
'pretty_version' => 'v1.23.0',
'version' => '1.23.0.0',
'pretty_version' => 'v1.25.0',
'version' => '1.25.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
'aliases' => array(),
'reference' => '46cd95797e9df938fdd2b03693b5fca5e64b01ce',
'reference' => '30885182c981ab175d4d034db0f6f469898070ab',
'dev_requirement' => false,
),
'symfony/polyfill-intl-grapheme' => array(
'pretty_version' => 'v1.23.1',
'version' => '1.23.1.0',
'pretty_version' => 'v1.25.0',
'version' => '1.25.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme',
'aliases' => array(),
'reference' => '16880ba9c5ebe3642d1995ab866db29270b36535',
'reference' => '81b86b50cf841a64252b439e738e97f4a34e2783',
'dev_requirement' => false,
),
'symfony/polyfill-intl-normalizer' => array(
'pretty_version' => 'v1.23.0',
'version' => '1.23.0.0',
'pretty_version' => 'v1.25.0',
'version' => '1.25.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer',
'aliases' => array(),
@ -185,81 +185,72 @@
'dev_requirement' => false,
),
'symfony/polyfill-mbstring' => array(
'pretty_version' => 'v1.23.1',
'version' => '1.23.1.0',
'pretty_version' => 'v1.25.0',
'version' => '1.25.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
'aliases' => array(),
'reference' => '9174a3d80210dca8daa7f31fec659150bbeabfc6',
'dev_requirement' => false,
),
'symfony/polyfill-php73' => array(
'pretty_version' => 'v1.23.0',
'version' => '1.23.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php73',
'aliases' => array(),
'reference' => 'fba8933c384d6476ab14fb7b8526e5287ca7e010',
'reference' => '0abb51d2f102e00a4eefcf46ba7fec406d245825',
'dev_requirement' => false,
),
'symfony/polyfill-php80' => array(
'pretty_version' => 'v1.23.1',
'version' => '1.23.1.0',
'pretty_version' => 'v1.25.0',
'version' => '1.25.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
'aliases' => array(),
'reference' => '1100343ed1a92e3a38f9ae122fc0eb21602547be',
'reference' => '4407588e0d3f1f52efb65fbe92babe41f37fe50c',
'dev_requirement' => false,
),
'symfony/service-contracts' => array(
'pretty_version' => 'v2.4.0',
'version' => '2.4.0.0',
'pretty_version' => 'v3.0.0',
'version' => '3.0.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/service-contracts',
'aliases' => array(),
'reference' => 'f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb',
'reference' => '36715ebf9fb9db73db0cb24263c79077c6fe8603',
'dev_requirement' => false,
),
'symfony/string' => array(
'pretty_version' => 'v5.3.3',
'version' => '5.3.3.0',
'pretty_version' => 'v6.0.3',
'version' => '6.0.3.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/string',
'aliases' => array(),
'reference' => 'bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1',
'reference' => '522144f0c4c004c80d56fa47e40e17028e2eefc2',
'dev_requirement' => false,
),
'symfony/translation' => array(
'pretty_version' => 'v5.3.4',
'version' => '5.3.4.0',
'pretty_version' => 'v6.0.6',
'version' => '6.0.6.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/translation',
'aliases' => array(),
'reference' => 'd89ad7292932c2699cbe4af98d72c5c6bbc504c1',
'reference' => 'f6639cb9b5e0c57fe31e3263b900a77eedb0c908',
'dev_requirement' => false,
),
'symfony/translation-contracts' => array(
'pretty_version' => 'v2.4.0',
'version' => '2.4.0.0',
'pretty_version' => 'v3.0.0',
'version' => '3.0.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/translation-contracts',
'aliases' => array(),
'reference' => '95c812666f3e91db75385749fe219c5e494c7f95',
'reference' => '1b6ea5a7442af5a12dba3dbd6d71034b5b234e77',
'dev_requirement' => false,
),
'symfony/translation-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '2.3',
0 => '2.3|3.0',
),
),
'voku/portable-ascii' => array(
'pretty_version' => '1.5.6',
'version' => '1.5.6.0',
'pretty_version' => '2.0.1',
'version' => '2.0.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../voku/portable-ascii',
'aliases' => array(),
'reference' => '80953678b19901e5165c56752d087fc11526017c',
'reference' => 'b56450eed252f6801410d810c8e1727224ae0743',
'dev_requirement' => false,
),
),

View file

@ -4,8 +4,8 @@
$issues = array();
if (!(PHP_VERSION_ID >= 70300)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.3.0". You are running ' . PHP_VERSION . '.';
if (!(PHP_VERSION_ID >= 80002)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.2". You are running ' . PHP_VERSION . '.';
}
if ($issues) {

View file

@ -3,6 +3,5 @@
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://travis-ci.org/doctrine/inflector.svg)](https://travis-ci.org/doctrine/inflector)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/doctrine/inflector/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/doctrine/inflector/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/doctrine/inflector/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/doctrine/inflector/?branch=master)
[![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)

View file

@ -16,11 +16,12 @@
"php": "^7.2 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^7.0",
"phpstan/phpstan": "^0.11",
"phpstan/phpstan-phpunit": "^0.11",
"phpstan/phpstan-strict-rules": "^0.11",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
"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": {
@ -31,10 +32,5 @@
"psr-4": {
"Doctrine\\Tests\\Inflector\\": "tests/Doctrine/Tests/Inflector"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
}
}

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\Inflector;
use Doctrine\Inflector\Rules\Ruleset;
use function array_unshift;
abstract class GenericLanguageInflectorFactory implements LanguageInflectorFactory

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\Inflector;
use RuntimeException;
use function chr;
use function function_exists;
use function lcfirst;

View file

@ -11,6 +11,7 @@ use Doctrine\Inflector\Rules\Portuguese;
use Doctrine\Inflector\Rules\Spanish;
use Doctrine\Inflector\Rules\Turkish;
use InvalidArgumentException;
use function sprintf;
final class InflectorFactory
@ -25,16 +26,22 @@ final class InflectorFactory
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.',

View file

@ -31,9 +31,10 @@ class Inflectible
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|lieu|pneu|sarrau)$/'), '\1s');
yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)$/'), '\1x');
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');
}

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\Inflector\Rules;
use Doctrine\Inflector\WordInflector;
use function strtolower;
use function strtoupper;
use function substr;

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\Inflector\Rules;
use Doctrine\Inflector\WordInflector;
use function preg_replace;
final class Transformation implements WordInflector

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\Inflector;
use Doctrine\Inflector\Rules\Ruleset;
use function array_merge;
/**

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

@ -121,6 +121,23 @@ class Arr
return $results;
}
/**
* Convert a flatten "dot" notation array into an expanded array.
*
* @param iterable $array
* @return array
*/
public static function undot($array)
{
$results = [];
foreach ($array as $key => $value) {
static::set($results, $key, $value);
}
return $results;
}
/**
* Get all of the given array except for a specified array of keys.
*
@ -297,7 +314,7 @@ class Arr
return $array[$key];
}
if (strpos($key, '.') === false) {
if (! str_contains($key, '.')) {
return $array[$key] ?? value($default);
}
@ -393,6 +410,31 @@ class Arr
return array_keys($keys) !== $keys;
}
/**
* Determines if an array is a list.
*
* An array is a "list" if all array keys are sequential integers starting from 0 with no gaps in between.
*
* @param array $array
* @return bool
*/
public static function isList($array)
{
return ! self::isAssoc($array);
}
/**
* Key an associative array by a field or using a callback.
*
* @param array $array
* @param callable|array|string
* @return array
*/
public static function keyBy($array, $keyBy)
{
return Collection::make($array)->keyBy($keyBy)->all();
}
/**
* Get a subset of the items from the given array.
*
@ -687,6 +729,19 @@ class Arr
return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
}
/**
* Filter items where the value is not null.
*
* @param array $array
* @return array
*/
public static function whereNotNull($array)
{
return static::where($array, function ($value) {
return ! is_null($value);
});
}
/**
* If the given value is not an array and not null, wrap it in one.
*

View file

@ -4,25 +4,37 @@ namespace Illuminate\Support;
use ArrayAccess;
use ArrayIterator;
use Illuminate\Contracts\Support\CanBeEscapedWhenCastToString;
use Illuminate\Support\Traits\EnumeratesValues;
use Illuminate\Support\Traits\Macroable;
use stdClass;
use Traversable;
class Collection implements ArrayAccess, Enumerable
/**
* @template TKey of array-key
* @template TValue
*
* @implements \ArrayAccess<TKey, TValue>
* @implements \Illuminate\Support\Enumerable<TKey, TValue>
*/
class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerable
{
/**
* @use \Illuminate\Support\Traits\EnumeratesValues<TKey, TValue>
*/
use EnumeratesValues, Macroable;
/**
* The items contained in the collection.
*
* @var array
* @var array<TKey, TValue>
*/
protected $items = [];
/**
* Create a new collection.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue>|null $items
* @return void
*/
public function __construct($items = [])
@ -35,7 +47,7 @@ class Collection implements ArrayAccess, Enumerable
*
* @param int $from
* @param int $to
* @return static
* @return static<int, int>
*/
public static function range($from, $to)
{
@ -45,7 +57,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get all of the items in the collection.
*
* @return array
* @return array<TKey, TValue>
*/
public function all()
{
@ -55,7 +67,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get a lazy collection for the items in this collection.
*
* @return \Illuminate\Support\LazyCollection
* @return \Illuminate\Support\LazyCollection<TKey, TValue>
*/
public function lazy()
{
@ -65,8 +77,8 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the average value of a given key.
*
* @param callable|string|null $callback
* @return mixed
* @param (callable(TValue): float|int)|string|null $callback
* @return float|int|null
*/
public function avg($callback = null)
{
@ -86,8 +98,8 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the median of a given key.
*
* @param string|array|null $key
* @return mixed
* @param string|array<array-key, string>|null $key
* @return float|int|null
*/
public function median($key = null)
{
@ -116,8 +128,8 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the mode of a given key.
*
* @param string|array|null $key
* @return array|null
* @param string|array<array-key, string>|null $key
* @return array<int, float|int>|null
*/
public function mode($key = null)
{
@ -145,7 +157,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Collapse the collection of items into a single array.
*
* @return static
* @return static<int, mixed>
*/
public function collapse()
{
@ -155,7 +167,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Determine if an item exists in the collection.
*
* @param mixed $key
* @param (callable(TValue, TKey): bool)|TValue|string $key
* @param mixed $operator
* @param mixed $value
* @return bool
@ -175,11 +187,27 @@ class Collection implements ArrayAccess, Enumerable
return $this->contains($this->operatorForWhere(...func_get_args()));
}
/**
* Determine if an item is not contained in the collection.
*
* @param mixed $key
* @param mixed $operator
* @param mixed $value
* @return bool
*/
public function doesntContain($key, $operator = null, $value = null)
{
return ! $this->contains(...func_get_args());
}
/**
* Cross join with the given lists, returning all possible permutations.
*
* @param mixed ...$lists
* @return static
* @template TCrossJoinKey
* @template TCrossJoinValue
*
* @param \Illuminate\Contracts\Support\Arrayable<TCrossJoinKey, TCrossJoinValue>|iterable<TCrossJoinKey, TCrossJoinValue> ...$lists
* @return static<int, array<int, TValue|TCrossJoinValue>>
*/
public function crossJoin(...$lists)
{
@ -191,7 +219,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the items in the collection that are not present in the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<array-key, TValue>|iterable<array-key, TValue> $items
* @return static
*/
public function diff($items)
@ -202,8 +230,8 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the items in the collection that are not present in the given items, using the callback.
*
* @param mixed $items
* @param callable $callback
* @param \Illuminate\Contracts\Support\Arrayable<array-key, TValue>|iterable<array-key, TValue> $items
* @param callable(TValue, TValue): int $callback
* @return static
*/
public function diffUsing($items, callable $callback)
@ -214,7 +242,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the items in the collection whose keys and values are not present in the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function diffAssoc($items)
@ -225,8 +253,8 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the items in the collection whose keys and values are not present in the given items, using the callback.
*
* @param mixed $items
* @param callable $callback
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @param callable(TKey, TKey): int $callback
* @return static
*/
public function diffAssocUsing($items, callable $callback)
@ -237,7 +265,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the items in the collection whose keys are not present in the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function diffKeys($items)
@ -248,8 +276,8 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the items in the collection whose keys are not present in the given items, using the callback.
*
* @param mixed $items
* @param callable $callback
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @param callable(TKey, TKey): int $callback
* @return static
*/
public function diffKeysUsing($items, callable $callback)
@ -260,7 +288,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Retrieve duplicate items from the collection.
*
* @param callable|string|null $callback
* @param (callable(TValue): bool)|string|null $callback
* @param bool $strict
* @return static
*/
@ -288,7 +316,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Retrieve duplicate items from the collection using strict comparison.
*
* @param callable|string|null $callback
* @param (callable(TValue): bool)|string|null $callback
* @return static
*/
public function duplicatesStrict($callback = null)
@ -300,7 +328,7 @@ class Collection implements ArrayAccess, Enumerable
* Get the comparison function to detect duplicates.
*
* @param bool $strict
* @return \Closure
* @return callable(TValue, TValue): bool
*/
protected function duplicateComparator($strict)
{
@ -318,7 +346,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get all items except for those with the specified keys.
*
* @param \Illuminate\Support\Collection|mixed $keys
* @param \Illuminate\Support\Enumerable<array-key, TKey>|array<array-key, TKey> $keys
* @return static
*/
public function except($keys)
@ -335,7 +363,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Run a filter over each of the items.
*
* @param callable|null $callback
* @param (callable(TValue, TKey): bool)|null $callback
* @return static
*/
public function filter(callable $callback = null)
@ -350,9 +378,11 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the first item from the collection passing the given truth test.
*
* @param callable|null $callback
* @param mixed $default
* @return mixed
* @template TFirstDefault
*
* @param (callable(TValue, TKey): bool)|null $callback
* @param TFirstDefault|(\Closure(): TFirstDefault) $default
* @return TValue|TFirstDefault
*/
public function first(callable $callback = null, $default = null)
{
@ -363,7 +393,7 @@ class Collection implements ArrayAccess, Enumerable
* Get a flattened array of the items in the collection.
*
* @param int $depth
* @return static
* @return static<int, mixed>
*/
public function flatten($depth = INF)
{
@ -373,7 +403,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Flip the items in the collection.
*
* @return static
* @return static<TValue, TKey>
*/
public function flip()
{
@ -383,7 +413,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Remove an item from the collection by key.
*
* @param string|array $keys
* @param TKey|array<array-key, TKey> $keys
* @return $this
*/
public function forget($keys)
@ -398,9 +428,11 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get an item from the collection by key.
*
* @param mixed $key
* @param mixed $default
* @return mixed
* @template TGetDefault
*
* @param TKey $key
* @param TGetDefault|(\Closure(): TGetDefault) $default
* @return TValue|TGetDefault
*/
public function get($key, $default = null)
{
@ -411,12 +443,30 @@ class Collection implements ArrayAccess, Enumerable
return value($default);
}
/**
* Get an item from the collection by key or add it to collection if it does not exist.
*
* @param mixed $key
* @param mixed $value
* @return mixed
*/
public function getOrPut($key, $value)
{
if (array_key_exists($key, $this->items)) {
return $this->items[$key];
}
$this->offsetSet($key, $value = value($value));
return $value;
}
/**
* Group an associative array by a field or using a callback.
*
* @param array|callable|string $groupBy
* @param (callable(TValue, TKey): array-key)|array|string $groupBy
* @param bool $preserveKeys
* @return static
* @return static<array-key, static<array-key, TValue>>
*/
public function groupBy($groupBy, $preserveKeys = false)
{
@ -460,8 +510,8 @@ class Collection implements ArrayAccess, Enumerable
/**
* Key an associative array by a field or using a callback.
*
* @param callable|string $keyBy
* @return static
* @param (callable(TValue, TKey): array-key)|array|string $keyBy
* @return static<array-key, TValue>
*/
public function keyBy($keyBy)
{
@ -485,7 +535,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Determine if an item exists in the collection by key.
*
* @param mixed $key
* @param TKey|array<array-key, TKey> $key
* @return bool
*/
public function has($key)
@ -501,6 +551,29 @@ class Collection implements ArrayAccess, Enumerable
return true;
}
/**
* Determine if any of the keys exist in the collection.
*
* @param mixed $key
* @return bool
*/
public function hasAny($key)
{
if ($this->isEmpty()) {
return false;
}
$keys = is_array($key) ? $key : func_get_args();
foreach ($keys as $value) {
if ($this->has($value)) {
return true;
}
}
return false;
}
/**
* Concatenate values of a given key as a string.
*
@ -522,7 +595,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Intersect the collection with the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function intersect($items)
@ -533,7 +606,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Intersect the collection with the given items by key.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function intersectByKeys($items)
@ -596,7 +669,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the keys of the collection items.
*
* @return static
* @return static<int, TKey>
*/
public function keys()
{
@ -606,9 +679,11 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the last item from the collection.
*
* @param callable|null $callback
* @param mixed $default
* @return mixed
* @template TLastDefault
*
* @param (callable(TValue, TKey): bool)|null $callback
* @param TLastDefault|(\Closure(): TLastDefault) $default
* @return TValue|TLastDefault
*/
public function last(callable $callback = null, $default = null)
{
@ -618,9 +693,9 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the values of a given key.
*
* @param string|array|int|null $value
* @param string|array<array-key, string> $value
* @param string|null $key
* @return static
* @return static<int, mixed>
*/
public function pluck($value, $key = null)
{
@ -630,8 +705,10 @@ class Collection implements ArrayAccess, Enumerable
/**
* Run a map over each of the items.
*
* @param callable $callback
* @return static
* @template TMapValue
*
* @param callable(TValue, TKey): TMapValue $callback
* @return static<TKey, TMapValue>
*/
public function map(callable $callback)
{
@ -647,8 +724,11 @@ class Collection implements ArrayAccess, Enumerable
*
* The callback should return an associative array with a single key/value pair.
*
* @param callable $callback
* @return static
* @template TMapToDictionaryKey of array-key
* @template TMapToDictionaryValue
*
* @param callable(TValue, TKey): array<TMapToDictionaryKey, TMapToDictionaryValue> $callback
* @return static<TMapToDictionaryKey, array<int, TMapToDictionaryValue>>
*/
public function mapToDictionary(callable $callback)
{
@ -676,8 +756,11 @@ class Collection implements ArrayAccess, Enumerable
*
* The callback should return an associative array with a single key/value pair.
*
* @param callable $callback
* @return static
* @template TMapWithKeysKey of array-key
* @template TMapWithKeysValue
*
* @param callable(TValue, TKey): array<TMapWithKeysKey, TMapWithKeysValue> $callback
* @return static<TMapWithKeysKey, TMapWithKeysValue>
*/
public function mapWithKeys(callable $callback)
{
@ -697,7 +780,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Merge the collection with the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function merge($items)
@ -708,8 +791,10 @@ class Collection implements ArrayAccess, Enumerable
/**
* Recursively merge the collection with the given items.
*
* @param mixed $items
* @return static
* @template TMergeRecursiveValue
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TMergeRecursiveValue>|iterable<TKey, TMergeRecursiveValue> $items
* @return static<TKey, TValue|TMergeRecursiveValue>
*/
public function mergeRecursive($items)
{
@ -719,8 +804,10 @@ class Collection implements ArrayAccess, Enumerable
/**
* Create a collection by using this collection for keys and another for its values.
*
* @param mixed $values
* @return static
* @template TCombineValue
*
* @param \Illuminate\Contracts\Support\Arrayable<array-key, TCombineValue>|iterable<array-key, TCombineValue> $values
* @return static<TKey, TCombineValue>
*/
public function combine($values)
{
@ -730,7 +817,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Union the collection with the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function union($items)
@ -765,7 +852,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the items with the specified keys.
*
* @param mixed $keys
* @param \Illuminate\Support\Enumerable<array-key, TKey>|array<array-key, TKey> $keys
* @return static
*/
public function only($keys)
@ -787,7 +874,7 @@ class Collection implements ArrayAccess, Enumerable
* Get and remove the last N items from the collection.
*
* @param int $count
* @return mixed
* @return static<int, TValue>|TValue|null
*/
public function pop($count = 1)
{
@ -813,8 +900,8 @@ class Collection implements ArrayAccess, Enumerable
/**
* Push an item onto the beginning of the collection.
*
* @param mixed $value
* @param mixed $key
* @param TValue $value
* @param TKey $key
* @return $this
*/
public function prepend($value, $key = null)
@ -827,7 +914,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Push one or more items onto the end of the collection.
*
* @param mixed $values [optional]
* @param TValue ...$values
* @return $this
*/
public function push(...$values)
@ -842,7 +929,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Push all of the given items onto the collection.
*
* @param iterable $source
* @param iterable<array-key, TValue> $source
* @return static
*/
public function concat($source)
@ -859,9 +946,11 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get and remove an item from the collection.
*
* @param mixed $key
* @param mixed $default
* @return mixed
* @template TPullDefault
*
* @param TKey $key
* @param TPullDefault|(\Closure(): TPullDefault) $default
* @return TValue|TPullDefault
*/
public function pull($key, $default = null)
{
@ -871,8 +960,8 @@ class Collection implements ArrayAccess, Enumerable
/**
* Put an item in the collection by key.
*
* @param mixed $key
* @param mixed $value
* @param TKey $key
* @param TValue $value
* @return $this
*/
public function put($key, $value)
@ -886,7 +975,7 @@ class Collection implements ArrayAccess, Enumerable
* Get one or a specified number of items randomly from the collection.
*
* @param int|null $number
* @return static|mixed
* @return static<int, TValue>|TValue
*
* @throws \InvalidArgumentException
*/
@ -902,7 +991,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Replace the collection items with the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function replace($items)
@ -913,7 +1002,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Recursively replace the collection items with the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function replaceRecursive($items)
@ -934,9 +1023,9 @@ class Collection implements ArrayAccess, Enumerable
/**
* Search the collection for a given value and return the corresponding key if successful.
*
* @param mixed $value
* @param TValue|(callable(TValue,TKey): bool) $value
* @param bool $strict
* @return mixed
* @return TKey|bool
*/
public function search($value, $strict = false)
{
@ -957,7 +1046,7 @@ class Collection implements ArrayAccess, Enumerable
* Get and remove the first N items from the collection.
*
* @param int $count
* @return mixed
* @return static<int, TValue>|TValue|null
*/
public function shift($count = 1)
{
@ -996,7 +1085,7 @@ class Collection implements ArrayAccess, Enumerable
*
* @param int $size
* @param int $step
* @return static
* @return static<int, static>
*/
public function sliding($size = 2, $step = 1)
{
@ -1021,7 +1110,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Skip items in the collection until the given condition is met.
*
* @param mixed $value
* @param TValue|callable(TValue,TKey): bool $value
* @return static
*/
public function skipUntil($value)
@ -1032,7 +1121,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Skip items in the collection while the given condition is met.
*
* @param mixed $value
* @param TValue|callable(TValue,TKey): bool $value
* @return static
*/
public function skipWhile($value)
@ -1056,7 +1145,7 @@ class Collection implements ArrayAccess, Enumerable
* Split a collection into a certain number of groups.
*
* @param int $numberOfGroups
* @return static
* @return static<int, static>
*/
public function split($numberOfGroups)
{
@ -1093,7 +1182,7 @@ class Collection implements ArrayAccess, Enumerable
* Split a collection into a certain number of groups, and fill the first groups completely.
*
* @param int $numberOfGroups
* @return static
* @return static<int, static>
*/
public function splitIn($numberOfGroups)
{
@ -1103,10 +1192,10 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception.
*
* @param mixed $key
* @param (callable(TValue, TKey): bool)|string $key
* @param mixed $operator
* @param mixed $value
* @return mixed
* @return TValue
*
* @throws \Illuminate\Support\ItemNotFoundException
* @throws \Illuminate\Support\MultipleItemsFoundException
@ -1117,14 +1206,16 @@ class Collection implements ArrayAccess, Enumerable
? $this->operatorForWhere(...func_get_args())
: $key;
$items = $this->when($filter)->filter($filter);
$items = $this->unless($filter == null)->filter($filter);
if ($items->isEmpty()) {
$count = $items->count();
if ($count === 0) {
throw new ItemNotFoundException;
}
if ($items->count() > 1) {
throw new MultipleItemsFoundException;
if ($count > 1) {
throw new MultipleItemsFoundException($count);
}
return $items->first();
@ -1133,10 +1224,10 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get the first item in the collection but throw an exception if no matching items exist.
*
* @param mixed $key
* @param (callable(TValue, TKey): bool)|string $key
* @param mixed $operator
* @param mixed $value
* @return mixed
* @return TValue
*
* @throws \Illuminate\Support\ItemNotFoundException
*/
@ -1161,7 +1252,7 @@ class Collection implements ArrayAccess, Enumerable
* Chunk the collection into chunks of the given size.
*
* @param int $size
* @return static
* @return static<int, static>
*/
public function chunk($size)
{
@ -1181,8 +1272,8 @@ class Collection implements ArrayAccess, Enumerable
/**
* Chunk the collection into chunks with a callback.
*
* @param callable $callback
* @return static
* @param callable(TValue, TKey, static<int, TValue>): bool $callback
* @return static<int, static<int, TValue>>
*/
public function chunkWhile(callable $callback)
{
@ -1194,7 +1285,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Sort through each item with a callback.
*
* @param callable|int|null $callback
* @param (callable(TValue, TValue): int)|null|int $callback
* @return static
*/
public function sort($callback = null)
@ -1226,7 +1317,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Sort the collection using the given callback.
*
* @param callable|array|string $callback
* @param array<array-key, (callable(TValue, TKey): mixed)|array<array-key, string>|(callable(TValue, TKey): mixed)|string $callback
* @param int $options
* @param bool $descending
* @return static
@ -1243,7 +1334,7 @@ class Collection implements ArrayAccess, Enumerable
// First we will loop through the items and get the comparator from a callback
// function which we were given. Then, we will sort the returned values and
// and grab the corresponding values for the sorted keys from this array.
// grab all the corresponding values for the sorted keys from this array.
foreach ($this->items as $key => $value) {
$results[$key] = $callback($value, $key);
}
@ -1264,7 +1355,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Sort the collection using multiple comparisons.
*
* @param array $comparisons
* @param array<array-key, (callable(TValue, TKey): mixed)|array<array-key, string> $comparisons
* @return static
*/
protected function sortByMany(array $comparisons = [])
@ -1280,9 +1371,7 @@ class Collection implements ArrayAccess, Enumerable
$ascending = Arr::get($comparison, 1, true) === true ||
Arr::get($comparison, 1, true) === 'asc';
$result = 0;
if (is_callable($prop)) {
if (! is_string($prop) && is_callable($prop)) {
$result = $prop($a, $b);
} else {
$values = [data_get($a, $prop), data_get($b, $prop)];
@ -1308,7 +1397,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Sort the collection in descending order using the given callback.
*
* @param callable|string $callback
* @param array<array-key, (callable(TValue, TKey): mixed)|array<array-key, string>|(callable(TValue, TKey): mixed)|string $callback
* @param int $options
* @return static
*/
@ -1344,12 +1433,27 @@ class Collection implements ArrayAccess, Enumerable
return $this->sortKeys($options, true);
}
/**
* Sort the collection keys using a callback.
*
* @param callable(TKey, TKey): int $callback
* @return static
*/
public function sortKeysUsing(callable $callback)
{
$items = $this->items;
uksort($items, $callback);
return new static($items);
}
/**
* Splice a portion of the underlying collection array.
*
* @param int $offset
* @param int|null $length
* @param mixed $replacement
* @param array<array-key, TValue> $replacement
* @return static
*/
public function splice($offset, $length = null, $replacement = [])
@ -1358,7 +1462,7 @@ class Collection implements ArrayAccess, Enumerable
return new static(array_splice($this->items, $offset));
}
return new static(array_splice($this->items, $offset, $length, $replacement));
return new static(array_splice($this->items, $offset, $length, $this->getArrayableItems($replacement)));
}
/**
@ -1379,7 +1483,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Take items in the collection until the given condition is met.
*
* @param mixed $value
* @param TValue|callable(TValue,TKey): bool $value
* @return static
*/
public function takeUntil($value)
@ -1390,7 +1494,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Take items in the collection while the given condition is met.
*
* @param mixed $value
* @param TValue|callable(TValue,TKey): bool $value
* @return static
*/
public function takeWhile($value)
@ -1401,7 +1505,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Transform each item in the collection using a callback.
*
* @param callable $callback
* @param callable(TValue, TKey): TValue $callback
* @return $this
*/
public function transform(callable $callback)
@ -1412,10 +1516,46 @@ class Collection implements ArrayAccess, Enumerable
}
/**
* Reset the keys on the underlying array.
* Convert a flatten "dot" notation array into an expanded array.
*
* @return static
*/
public function undot()
{
return new static(Arr::undot($this->all()));
}
/**
* Return only unique items from the collection array.
*
* @param (callable(TValue, TKey): mixed)|string|null $key
* @param bool $strict
* @return static
*/
public function unique($key = null, $strict = false)
{
if (is_null($key) && $strict === false) {
return new static(array_unique($this->items, SORT_REGULAR));
}
$callback = $this->valueRetriever($key);
$exists = [];
return $this->reject(function ($item, $key) use ($callback, $strict, &$exists) {
if (in_array($id = $callback($item, $key), $exists, $strict)) {
return true;
}
$exists[] = $id;
});
}
/**
* Reset the keys on the underlying array.
*
* @return static<int, TValue>
*/
public function values()
{
return new static(array_values($this->items));
@ -1427,8 +1567,10 @@ class Collection implements ArrayAccess, Enumerable
* e.g. new Collection([1, 2, 3])->zip([4, 5, 6]);
* => [[1, 4], [2, 5], [3, 6]]
*
* @param mixed ...$items
* @return static
* @template TZipValue
*
* @param \Illuminate\Contracts\Support\Arrayable<array-key, TZipValue>|iterable<array-key, TZipValue> ...$items
* @return static<int, static<int, TValue|TZipValue>>
*/
public function zip($items)
{
@ -1446,9 +1588,11 @@ class Collection implements ArrayAccess, Enumerable
/**
* Pad collection to the specified length with a value.
*
* @template TPadValue
*
* @param int $size
* @param mixed $value
* @return static
* @param TPadValue $value
* @return static<int, TValue|TPadValue>
*/
public function pad($size, $value)
{
@ -1458,10 +1602,9 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get an iterator for the items.
*
* @return \ArrayIterator
* @return \ArrayIterator<TKey, TValue>
*/
#[\ReturnTypeWillChange]
public function getIterator()
public function getIterator(): Traversable
{
return new ArrayIterator($this->items);
}
@ -1471,8 +1614,7 @@ class Collection implements ArrayAccess, Enumerable
*
* @return int
*/
#[\ReturnTypeWillChange]
public function count()
public function count(): int
{
return count($this->items);
}
@ -1480,8 +1622,8 @@ class Collection implements ArrayAccess, Enumerable
/**
* Count the number of items in the collection by a field or using a callback.
*
* @param callable|string $countBy
* @return static
* @param (callable(TValue, TKey): mixed)|string|null $countBy
* @return static<array-key, int>
*/
public function countBy($countBy = null)
{
@ -1491,7 +1633,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Add an item to the collection.
*
* @param mixed $item
* @param TValue $item
* @return $this
*/
public function add($item)
@ -1504,7 +1646,7 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get a base Support collection instance from this collection.
*
* @return \Illuminate\Support\Collection
* @return \Illuminate\Support\Collection<TKey, TValue>
*/
public function toBase()
{
@ -1514,11 +1656,10 @@ class Collection implements ArrayAccess, Enumerable
/**
* Determine if an item exists at an offset.
*
* @param mixed $key
* @param TKey $key
* @return bool
*/
#[\ReturnTypeWillChange]
public function offsetExists($key)
public function offsetExists($key): bool
{
return isset($this->items[$key]);
}
@ -1526,11 +1667,10 @@ class Collection implements ArrayAccess, Enumerable
/**
* Get an item at a given offset.
*
* @param mixed $key
* @return mixed
* @param TKey $key
* @return TValue
*/
#[\ReturnTypeWillChange]
public function offsetGet($key)
public function offsetGet($key): mixed
{
return $this->items[$key];
}
@ -1538,12 +1678,11 @@ class Collection implements ArrayAccess, Enumerable
/**
* Set the item at a given offset.
*
* @param mixed $key
* @param mixed $value
* @param TKey|null $key
* @param TValue $value
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetSet($key, $value)
public function offsetSet($key, $value): void
{
if (is_null($key)) {
$this->items[] = $value;
@ -1555,11 +1694,10 @@ class Collection implements ArrayAccess, Enumerable
/**
* Unset the item at a given offset.
*
* @param string $key
* @param TKey $key
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetUnset($key)
public function offsetUnset($key): void
{
unset($this->items[$key]);
}

File diff suppressed because it is too large Load diff

View file

@ -5,26 +5,39 @@ namespace Illuminate\Support;
use ArrayIterator;
use Closure;
use DateTimeInterface;
use Generator;
use Illuminate\Contracts\Support\CanBeEscapedWhenCastToString;
use Illuminate\Support\Traits\EnumeratesValues;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
use IteratorAggregate;
use stdClass;
use Traversable;
class LazyCollection implements Enumerable
/**
* @template TKey of array-key
* @template TValue
*
* @implements \Illuminate\Support\Enumerable<TKey, TValue>
*/
class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable
{
/**
* @use \Illuminate\Support\Traits\EnumeratesValues<TKey, TValue>
*/
use EnumeratesValues, Macroable;
/**
* The source from which to generate items.
*
* @var callable|static
* @var (Closure(): \Generator<TKey, TValue, mixed, void>)|static|array<TKey, TValue>
*/
public $source;
/**
* Create a new lazy collection instance.
*
* @param mixed $source
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue>|(Closure(): \Generator<TKey, TValue, mixed, void>)|self<TKey, TValue>|array<TKey, TValue>|null $source
* @return void
*/
public function __construct($source = null)
@ -33,17 +46,35 @@ class LazyCollection implements Enumerable
$this->source = $source;
} elseif (is_null($source)) {
$this->source = static::empty();
} elseif ($source instanceof Generator) {
throw new InvalidArgumentException(
'Generators should not be passed directly to LazyCollection. Instead, pass a generator function.'
);
} else {
$this->source = $this->getArrayableItems($source);
}
}
/**
* Create a new collection instance if the value isn't one already.
*
* @template TMakeKey of array-key
* @template TMakeValue
*
* @param \Illuminate\Contracts\Support\Arrayable<TMakeKey, TMakeValue>|iterable<TMakeKey, TMakeValue>|(Closure(): \Generator<TMakeKey, TMakeValue, mixed, void>)|self<TMakeKey, TMakeValue>|array<TMakeKey, TMakeValue>|null $items
* @return static<TMakeKey, TMakeValue>
*/
public static function make($items = [])
{
return new static($items);
}
/**
* Create a collection with the given range.
*
* @param int $from
* @param int $to
* @return static
* @return static<int, int>
*/
public static function range($from, $to)
{
@ -63,7 +94,7 @@ class LazyCollection implements Enumerable
/**
* Get all items in the enumerable.
*
* @return array
* @return array<TKey, TValue>
*/
public function all()
{
@ -125,8 +156,8 @@ class LazyCollection implements Enumerable
/**
* Get the average value of a given key.
*
* @param callable|string|null $callback
* @return mixed
* @param (callable(TValue): float|int)|string|null $callback
* @return float|int|null
*/
public function avg($callback = null)
{
@ -136,8 +167,8 @@ class LazyCollection implements Enumerable
/**
* Get the median of a given key.
*
* @param string|array|null $key
* @return mixed
* @param string|array<array-key, string>|null $key
* @return float|int|null
*/
public function median($key = null)
{
@ -147,8 +178,8 @@ class LazyCollection implements Enumerable
/**
* Get the mode of a given key.
*
* @param string|array|null $key
* @return array|null
* @param string|array<string>|null $key
* @return array<int, float|int>|null
*/
public function mode($key = null)
{
@ -158,7 +189,7 @@ class LazyCollection implements Enumerable
/**
* Collapse the collection of items into a single array.
*
* @return static
* @return static<int, mixed>
*/
public function collapse()
{
@ -176,7 +207,7 @@ class LazyCollection implements Enumerable
/**
* Determine if an item exists in the enumerable.
*
* @param mixed $key
* @param (callable(TValue, TKey): bool)|TValue|string $key
* @param mixed $operator
* @param mixed $value
* @return bool
@ -186,6 +217,7 @@ class LazyCollection implements Enumerable
if (func_num_args() === 1 && $this->useAsCallable($key)) {
$placeholder = new stdClass;
/** @var callable $key */
return $this->first($key, $placeholder) !== $placeholder;
}
@ -204,11 +236,27 @@ class LazyCollection implements Enumerable
return $this->contains($this->operatorForWhere(...func_get_args()));
}
/**
* Determine if an item is not contained in the enumerable.
*
* @param mixed $key
* @param mixed $operator
* @param mixed $value
* @return bool
*/
public function doesntContain($key, $operator = null, $value = null)
{
return ! $this->contains(...func_get_args());
}
/**
* Cross join the given iterables, returning all possible permutations.
*
* @param array ...$arrays
* @return static
* @template TCrossJoinKey
* @template TCrossJoinValue
*
* @param \Illuminate\Contracts\Support\Arrayable<TCrossJoinKey, TCrossJoinValue>|iterable<TCrossJoinKey, TCrossJoinValue> ...$arrays
* @return static<int, array<int, TValue|TCrossJoinValue>>
*/
public function crossJoin(...$arrays)
{
@ -218,8 +266,8 @@ class LazyCollection implements Enumerable
/**
* Count the number of items in the collection by a field or using a callback.
*
* @param callable|string $countBy
* @return static
* @param (callable(TValue, TKey): mixed)|string|null $countBy
* @return static<array-key, int>
*/
public function countBy($countBy = null)
{
@ -247,7 +295,7 @@ class LazyCollection implements Enumerable
/**
* Get the items that are not present in the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<array-key, TValue>|iterable<array-key, TValue> $items
* @return static
*/
public function diff($items)
@ -258,8 +306,8 @@ class LazyCollection implements Enumerable
/**
* Get the items that are not present in the given items, using the callback.
*
* @param mixed $items
* @param callable $callback
* @param \Illuminate\Contracts\Support\Arrayable<array-key, TValue>|iterable<array-key, TValue> $items
* @param callable(TValue, TValue): int $callback
* @return static
*/
public function diffUsing($items, callable $callback)
@ -270,7 +318,7 @@ class LazyCollection implements Enumerable
/**
* Get the items whose keys and values are not present in the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function diffAssoc($items)
@ -281,8 +329,8 @@ class LazyCollection implements Enumerable
/**
* Get the items whose keys and values are not present in the given items, using the callback.
*
* @param mixed $items
* @param callable $callback
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @param callable(TKey, TKey): int $callback
* @return static
*/
public function diffAssocUsing($items, callable $callback)
@ -293,7 +341,7 @@ class LazyCollection implements Enumerable
/**
* Get the items whose keys are not present in the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function diffKeys($items)
@ -304,8 +352,8 @@ class LazyCollection implements Enumerable
/**
* Get the items whose keys are not present in the given items, using the callback.
*
* @param mixed $items
* @param callable $callback
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @param callable(TKey, TKey): int $callback
* @return static
*/
public function diffKeysUsing($items, callable $callback)
@ -316,7 +364,7 @@ class LazyCollection implements Enumerable
/**
* Retrieve duplicate items.
*
* @param callable|string|null $callback
* @param (callable(TValue): bool)|string|null $callback
* @param bool $strict
* @return static
*/
@ -328,7 +376,7 @@ class LazyCollection implements Enumerable
/**
* Retrieve duplicate items using strict comparison.
*
* @param callable|string|null $callback
* @param (callable(TValue): bool)|string|null $callback
* @return static
*/
public function duplicatesStrict($callback = null)
@ -339,7 +387,7 @@ class LazyCollection implements Enumerable
/**
* Get all items except for those with the specified keys.
*
* @param mixed $keys
* @param \Illuminate\Support\Enumerable<array-key, TKey>|array<array-key, TKey> $keys
* @return static
*/
public function except($keys)
@ -350,7 +398,7 @@ class LazyCollection implements Enumerable
/**
* Run a filter over each of the items.
*
* @param callable|null $callback
* @param (callable(TValue): bool)|null $callback
* @return static
*/
public function filter(callable $callback = null)
@ -373,9 +421,11 @@ class LazyCollection implements Enumerable
/**
* Get the first item from the enumerable passing the given truth test.
*
* @param callable|null $callback
* @param mixed $default
* @return mixed
* @template TFirstDefault
*
* @param (callable(TValue): bool)|null $callback
* @param TFirstDefault|(\Closure(): TFirstDefault) $default
* @return TValue|TFirstDefault
*/
public function first(callable $callback = null, $default = null)
{
@ -402,7 +452,7 @@ class LazyCollection implements Enumerable
* Get a flattened list of the items in the collection.
*
* @param int $depth
* @return static
* @return static<int, mixed>
*/
public function flatten($depth = INF)
{
@ -424,7 +474,7 @@ class LazyCollection implements Enumerable
/**
* Flip the items in the collection.
*
* @return static
* @return static<TValue, TKey>
*/
public function flip()
{
@ -438,9 +488,11 @@ class LazyCollection implements Enumerable
/**
* Get an item by key.
*
* @param mixed $key
* @param mixed $default
* @return mixed
* @template TGetDefault
*
* @param TKey|null $key
* @param TGetDefault|(\Closure(): TGetDefault) $default
* @return TValue|TGetDefault
*/
public function get($key, $default = null)
{
@ -460,9 +512,9 @@ class LazyCollection implements Enumerable
/**
* Group an associative array by a field or using a callback.
*
* @param array|callable|string $groupBy
* @param (callable(TValue, TKey): array-key)|array|string $groupBy
* @param bool $preserveKeys
* @return static
* @return static<array-key, static<array-key, TValue>>
*/
public function groupBy($groupBy, $preserveKeys = false)
{
@ -472,8 +524,8 @@ class LazyCollection implements Enumerable
/**
* Key an associative array by a field or using a callback.
*
* @param callable|string $keyBy
* @return static
* @param (callable(TValue, TKey): array-key)|array|string $keyBy
* @return static<array-key, TValue>
*/
public function keyBy($keyBy)
{
@ -512,6 +564,25 @@ class LazyCollection implements Enumerable
return false;
}
/**
* Determine if any of the keys exist in the collection.
*
* @param mixed $key
* @return bool
*/
public function hasAny($key)
{
$keys = array_flip(is_array($key) ? $key : func_get_args());
foreach ($this as $key => $value) {
if (array_key_exists($key, $keys)) {
return true;
}
}
return false;
}
/**
* Concatenate values of a given key as a string.
*
@ -527,7 +598,7 @@ class LazyCollection implements Enumerable
/**
* Intersect the collection with the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function intersect($items)
@ -538,7 +609,7 @@ class LazyCollection implements Enumerable
/**
* Intersect the collection with the given items by key.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function intersectByKeys($items)
@ -581,7 +652,7 @@ class LazyCollection implements Enumerable
/**
* Get the keys of the collection items.
*
* @return static
* @return static<int, TKey>
*/
public function keys()
{
@ -595,9 +666,11 @@ class LazyCollection implements Enumerable
/**
* Get the last item from the collection.
*
* @param callable|null $callback
* @param mixed $default
* @return mixed
* @template TLastDefault
*
* @param (callable(TValue, TKey): bool)|null $callback
* @param TLastDefault|(\Closure(): TLastDefault) $default
* @return TValue|TLastDefault
*/
public function last(callable $callback = null, $default = null)
{
@ -615,9 +688,9 @@ class LazyCollection implements Enumerable
/**
* Get the values of a given key.
*
* @param string|array $value
* @param string|array<array-key, string> $value
* @param string|null $key
* @return static
* @return static<int, mixed>
*/
public function pluck($value, $key = null)
{
@ -645,8 +718,10 @@ class LazyCollection implements Enumerable
/**
* Run a map over each of the items.
*
* @param callable $callback
* @return static
* @template TMapValue
*
* @param callable(TValue, TKey): TMapValue $callback
* @return static<TKey, TMapValue>
*/
public function map(callable $callback)
{
@ -662,8 +737,11 @@ class LazyCollection implements Enumerable
*
* The callback should return an associative array with a single key/value pair.
*
* @param callable $callback
* @return static
* @template TMapToDictionaryKey of array-key
* @template TMapToDictionaryValue
*
* @param callable(TValue, TKey): array<TMapToDictionaryKey, TMapToDictionaryValue> $callback
* @return static<TMapToDictionaryKey, array<int, TMapToDictionaryValue>>
*/
public function mapToDictionary(callable $callback)
{
@ -675,8 +753,11 @@ class LazyCollection implements Enumerable
*
* The callback should return an associative array with a single key/value pair.
*
* @param callable $callback
* @return static
* @template TMapWithKeysKey of array-key
* @template TMapWithKeysValue
*
* @param callable(TValue, TKey): array<TMapWithKeysKey, TMapWithKeysValue> $callback
* @return static<TMapWithKeysKey, TMapWithKeysValue>
*/
public function mapWithKeys(callable $callback)
{
@ -690,7 +771,7 @@ class LazyCollection implements Enumerable
/**
* Merge the collection with the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function merge($items)
@ -701,8 +782,10 @@ class LazyCollection implements Enumerable
/**
* Recursively merge the collection with the given items.
*
* @param mixed $items
* @return static
* @template TMergeRecursiveValue
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TMergeRecursiveValue>|iterable<TKey, TMergeRecursiveValue> $items
* @return static<TKey, TValue|TMergeRecursiveValue>
*/
public function mergeRecursive($items)
{
@ -712,8 +795,10 @@ class LazyCollection implements Enumerable
/**
* Create a collection by using this collection for keys and another for its values.
*
* @param mixed $values
* @return static
* @template TCombineValue
*
* @param \IteratorAggregate<array-key, TCombineValue>|array<array-key, TCombineValue>|(callable(): \Generator<array-key, TCombineValue>) $values
* @return static<TKey, TCombineValue>
*/
public function combine($values)
{
@ -743,7 +828,7 @@ class LazyCollection implements Enumerable
/**
* Union the collection with the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function union($items)
@ -776,7 +861,7 @@ class LazyCollection implements Enumerable
/**
* Get the items with the specified keys.
*
* @param mixed $keys
* @param \Illuminate\Support\Enumerable<array-key, TKey>|array<array-key, TKey> $keys
* @return static
*/
public function only($keys)
@ -811,7 +896,7 @@ class LazyCollection implements Enumerable
/**
* Push all of the given items onto the collection.
*
* @param iterable $source
* @param iterable<array-key, TValue> $source
* @return static
*/
public function concat($source)
@ -826,7 +911,7 @@ class LazyCollection implements Enumerable
* Get one or a specified number of items randomly from the collection.
*
* @param int|null $number
* @return static|mixed
* @return static<int, TValue>|TValue
*
* @throws \InvalidArgumentException
*/
@ -840,7 +925,7 @@ class LazyCollection implements Enumerable
/**
* Replace the collection items with the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function replace($items)
@ -867,7 +952,7 @@ class LazyCollection implements Enumerable
/**
* Recursively replace the collection items with the given items.
*
* @param mixed $items
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function replaceRecursive($items)
@ -888,12 +973,13 @@ class LazyCollection implements Enumerable
/**
* Search the collection for a given value and return the corresponding key if successful.
*
* @param mixed $value
* @param TValue|(callable(TValue,TKey): bool) $value
* @param bool $strict
* @return mixed
* @return TKey|bool
*/
public function search($value, $strict = false)
{
/** @var (callable(TValue,TKey): bool) $predicate */
$predicate = $this->useAsCallable($value)
? $value
: function ($item) use ($value, $strict) {
@ -925,7 +1011,7 @@ class LazyCollection implements Enumerable
*
* @param int $size
* @param int $step
* @return static
* @return static<int, static>
*/
public function sliding($size = 2, $step = 1)
{
@ -938,7 +1024,7 @@ class LazyCollection implements Enumerable
$chunk[$iterator->key()] = $iterator->current();
if (count($chunk) == $size) {
yield tap(new static($chunk), function () use (&$chunk, $step) {
yield (new static($chunk))->tap(function () use (&$chunk, $step) {
$chunk = array_slice($chunk, $step, null, true);
});
@ -985,7 +1071,7 @@ class LazyCollection implements Enumerable
/**
* Skip items in the collection until the given condition is met.
*
* @param mixed $value
* @param TValue|callable(TValue,TKey): bool $value
* @return static
*/
public function skipUntil($value)
@ -998,7 +1084,7 @@ class LazyCollection implements Enumerable
/**
* Skip items in the collection while the given condition is met.
*
* @param mixed $value
* @param TValue|callable(TValue,TKey): bool $value
* @return static
*/
public function skipWhile($value)
@ -1042,7 +1128,7 @@ class LazyCollection implements Enumerable
* Split a collection into a certain number of groups.
*
* @param int $numberOfGroups
* @return static
* @return static<int, static>
*/
public function split($numberOfGroups)
{
@ -1052,10 +1138,10 @@ class LazyCollection implements Enumerable
/**
* Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception.
*
* @param mixed $key
* @param (callable(TValue, TKey): bool)|string $key
* @param mixed $operator
* @param mixed $value
* @return mixed
* @return TValue
*
* @throws \Illuminate\Support\ItemNotFoundException
* @throws \Illuminate\Support\MultipleItemsFoundException
@ -1067,7 +1153,7 @@ class LazyCollection implements Enumerable
: $key;
return $this
->when($filter)
->unless($filter == null)
->filter($filter)
->take(2)
->collect()
@ -1077,10 +1163,10 @@ class LazyCollection implements Enumerable
/**
* Get the first item in the collection but throw an exception if no matching items exist.
*
* @param mixed $key
* @param (callable(TValue, TKey): bool)|string $key
* @param mixed $operator
* @param mixed $value
* @return mixed
* @return TValue
*
* @throws \Illuminate\Support\ItemNotFoundException
*/
@ -1091,7 +1177,7 @@ class LazyCollection implements Enumerable
: $key;
return $this
->when($filter)
->unless($filter == null)
->filter($filter)
->take(1)
->collect()
@ -1102,7 +1188,7 @@ class LazyCollection implements Enumerable
* Chunk the collection into chunks of the given size.
*
* @param int $size
* @return static
* @return static<int, static>
*/
public function chunk($size)
{
@ -1141,7 +1227,7 @@ class LazyCollection implements Enumerable
* Split a collection into a certain number of groups, and fill the first groups completely.
*
* @param int $numberOfGroups
* @return static
* @return static<int, static>
*/
public function splitIn($numberOfGroups)
{
@ -1151,8 +1237,8 @@ class LazyCollection implements Enumerable
/**
* Chunk the collection into chunks with a callback.
*
* @param callable $callback
* @return static
* @param callable(TValue, TKey, Collection<TKey, TValue>): bool $callback
* @return static<int, static<int, TValue>>
*/
public function chunkWhile(callable $callback)
{
@ -1188,7 +1274,7 @@ class LazyCollection implements Enumerable
/**
* Sort through each item with a callback.
*
* @param callable|null|int $callback
* @param (callable(TValue, TValue): int)|null|int $callback
* @return static
*/
public function sort($callback = null)
@ -1210,7 +1296,7 @@ class LazyCollection implements Enumerable
/**
* Sort the collection using the given callback.
*
* @param callable|string $callback
* @param array<array-key, (callable(TValue, TKey): mixed)|array<array-key, string>|(callable(TValue, TKey): mixed)|string $callback
* @param int $options
* @param bool $descending
* @return static
@ -1223,7 +1309,7 @@ class LazyCollection implements Enumerable
/**
* Sort the collection in descending order using the given callback.
*
* @param callable|string $callback
* @param array<array-key, (callable(TValue, TKey): mixed)|array<array-key, string>|(callable(TValue, TKey): mixed)|string $callback
* @param int $options
* @return static
*/
@ -1255,6 +1341,17 @@ class LazyCollection implements Enumerable
return $this->passthru('sortKeysDesc', func_get_args());
}
/**
* Sort the collection keys using a callback.
*
* @param callable(TKey, TKey): int $callback
* @return static
*/
public function sortKeysUsing(callable $callback)
{
return $this->passthru('sortKeysUsing', func_get_args());
}
/**
* Take the first or last {$limit} items.
*
@ -1287,11 +1384,12 @@ class LazyCollection implements Enumerable
/**
* Take items in the collection until the given condition is met.
*
* @param mixed $value
* @param TValue|callable(TValue,TKey): bool $value
* @return static
*/
public function takeUntil($value)
{
/** @var callable(TValue, TKey): bool $callback */
$callback = $this->useAsCallable($value) ? $value : $this->equality($value);
return new static(function () use ($callback) {
@ -1315,19 +1413,30 @@ class LazyCollection implements Enumerable
{
$timeout = $timeout->getTimestamp();
return $this->takeWhile(function () use ($timeout) {
return $this->now() < $timeout;
return new static(function () use ($timeout) {
if ($this->now() >= $timeout) {
return;
}
foreach ($this as $key => $value) {
yield $key => $value;
if ($this->now() >= $timeout) {
break;
}
}
});
}
/**
* Take items in the collection while the given condition is met.
*
* @param mixed $value
* @param TValue|callable(TValue,TKey): bool $value
* @return static
*/
public function takeWhile($value)
{
/** @var callable(TValue, TKey): bool $callback */
$callback = $this->useAsCallable($value) ? $value : $this->equality($value);
return $this->takeUntil(function ($item, $key) use ($callback) {
@ -1338,7 +1447,7 @@ class LazyCollection implements Enumerable
/**
* Pass each item in the collection to the given callback, lazily.
*
* @param callable $callback
* @param callable(TValue, TKey): mixed $callback
* @return static
*/
public function tapEach(callable $callback)
@ -1353,10 +1462,44 @@ class LazyCollection implements Enumerable
}
/**
* Reset the keys on the underlying array.
* Convert a flatten "dot" notation array into an expanded array.
*
* @return static
*/
public function undot()
{
return $this->passthru('undot', []);
}
/**
* Return only unique items from the collection array.
*
* @param (callable(TValue, TKey): mixed)|string|null $key
* @param bool $strict
* @return static
*/
public function unique($key = null, $strict = false)
{
$callback = $this->valueRetriever($key);
return new static(function () use ($callback, $strict) {
$exists = [];
foreach ($this as $key => $item) {
if (! in_array($id = $callback($item, $key), $exists, $strict)) {
yield $key => $item;
$exists[] = $id;
}
}
});
}
/**
* Reset the keys on the underlying array.
*
* @return static<int, TValue>
*/
public function values()
{
return new static(function () {
@ -1372,8 +1515,10 @@ class LazyCollection implements Enumerable
* e.g. new LazyCollection([1, 2, 3])->zip([4, 5, 6]);
* => [[1, 4], [2, 5], [3, 6]]
*
* @param mixed ...$items
* @return static
* @template TZipValue
*
* @param \Illuminate\Contracts\Support\Arrayable<array-key, TZipValue>|iterable<array-key, TZipValue> ...$items
* @return static<int, static<int, TValue|TZipValue>>
*/
public function zip($items)
{
@ -1395,9 +1540,11 @@ class LazyCollection implements Enumerable
/**
* Pad collection to the specified length with a value.
*
* @template TPadValue
*
* @param int $size
* @param mixed $value
* @return static
* @param TPadValue $value
* @return static<int, TValue|TPadValue>
*/
public function pad($size, $value)
{
@ -1423,10 +1570,9 @@ class LazyCollection implements Enumerable
/**
* Get the values iterator.
*
* @return \Traversable
* @return \Traversable<TKey, TValue>
*/
#[\ReturnTypeWillChange]
public function getIterator()
public function getIterator(): Traversable
{
return $this->makeIterator($this->source);
}
@ -1436,8 +1582,7 @@ class LazyCollection implements Enumerable
*
* @return int
*/
#[\ReturnTypeWillChange]
public function count()
public function count(): int
{
if (is_array($this->source)) {
return count($this->source);
@ -1449,8 +1594,11 @@ class LazyCollection implements Enumerable
/**
* Make an iterator from the given source.
*
* @param mixed $source
* @return \Traversable
* @template TIteratorKey of array-key
* @template TIteratorValue
*
* @param \IteratorAggregate<TIteratorKey, TIteratorValue>|array<TIteratorKey, TIteratorValue>|(callable(): \Generator<TIteratorKey, TIteratorValue>) $source
* @return \Traversable<TIteratorKey, TIteratorValue>
*/
protected function makeIterator($source)
{
@ -1468,9 +1616,9 @@ class LazyCollection implements Enumerable
/**
* Explode the "value" and "key" arguments passed to "pluck".
*
* @param string|array $value
* @param string|array|null $key
* @return array
* @param string|string[] $value
* @param string|string[]|null $key
* @return array{string[],string[]|null}
*/
protected function explodePluckParameters($value, $key)
{
@ -1485,7 +1633,7 @@ class LazyCollection implements Enumerable
* Pass this lazy collection through a method on the collection class.
*
* @param string $method
* @param array $params
* @param array<mixed> $params
* @return static
*/
protected function passthru($method, array $params)

View file

@ -6,4 +6,35 @@ use RuntimeException;
class MultipleItemsFoundException extends RuntimeException
{
/**
* The number of items found.
*
* @var int
*/
public $count;
/**
* Create a new exception instance.
*
* @param int $count
* @param int $code
* @param \Throwable|null $previous
* @return void
*/
public function __construct($count, $code = 0, $previous = null)
{
$this->count = $count;
parent::__construct("$count items were found.", $code, $previous);
}
/**
* Get the number of items found.
*
* @return int
*/
public function getCount()
{
return $this->count;
}
}

View file

@ -11,15 +11,19 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Enumerable;
use Illuminate\Support\HigherOrderCollectionProxy;
use Illuminate\Support\HigherOrderWhenProxy;
use JsonSerializable;
use Symfony\Component\VarDumper\VarDumper;
use Traversable;
use UnexpectedValueException;
/**
* @template TKey of array-key
* @template TValue
*
* @property-read HigherOrderCollectionProxy $average
* @property-read HigherOrderCollectionProxy $avg
* @property-read HigherOrderCollectionProxy $contains
* @property-read HigherOrderCollectionProxy $doesntContain
* @property-read HigherOrderCollectionProxy $each
* @property-read HigherOrderCollectionProxy $every
* @property-read HigherOrderCollectionProxy $filter
@ -32,28 +36,40 @@ use Traversable;
* @property-read HigherOrderCollectionProxy $min
* @property-read HigherOrderCollectionProxy $partition
* @property-read HigherOrderCollectionProxy $reject
* @property-read HigherOrderCollectionProxy $skipUntil
* @property-read HigherOrderCollectionProxy $skipWhile
* @property-read HigherOrderCollectionProxy $some
* @property-read HigherOrderCollectionProxy $sortBy
* @property-read HigherOrderCollectionProxy $sortByDesc
* @property-read HigherOrderCollectionProxy $skipUntil
* @property-read HigherOrderCollectionProxy $skipWhile
* @property-read HigherOrderCollectionProxy $sum
* @property-read HigherOrderCollectionProxy $takeUntil
* @property-read HigherOrderCollectionProxy $takeWhile
* @property-read HigherOrderCollectionProxy $unique
* @property-read HigherOrderCollectionProxy $unless
* @property-read HigherOrderCollectionProxy $until
* @property-read HigherOrderCollectionProxy $when
*/
trait EnumeratesValues
{
use Conditionable;
/**
* Indicates that the object's string representation should be escaped when __toString is invoked.
*
* @var bool
*/
protected $escapeWhenCastingToString = false;
/**
* The methods that can be proxied.
*
* @var string[]
* @var array<int, string>
*/
protected static $proxies = [
'average',
'avg',
'contains',
'doesntContain',
'each',
'every',
'filter',
@ -75,14 +91,19 @@ trait EnumeratesValues
'takeUntil',
'takeWhile',
'unique',
'unless',
'until',
'when',
];
/**
* Create a new collection instance if the value isn't one already.
*
* @param mixed $items
* @return static
* @template TMakeKey of array-key
* @template TMakeValue
*
* @param \Illuminate\Contracts\Support\Arrayable<TMakeKey, TMakeValue>|iterable<TMakeKey, TMakeValue>|null $items
* @return static<TMakeKey, TMakeValue>
*/
public static function make($items = [])
{
@ -92,8 +113,11 @@ trait EnumeratesValues
/**
* Wrap the given value in a collection if applicable.
*
* @param mixed $value
* @return static
* @template TWrapKey of array-key
* @template TWrapValue
*
* @param iterable<TWrapKey, TWrapValue> $value
* @return static<TWrapKey, TWrapValue>
*/
public static function wrap($value)
{
@ -105,8 +129,11 @@ trait EnumeratesValues
/**
* Get the underlying items from the given collection if applicable.
*
* @param array|static $value
* @return array
* @template TUnwrapKey of array-key
* @template TUnwrapValue
*
* @param array<TUnwrapKey, TUnwrapValue>|static<TUnwrapKey, TUnwrapValue> $value
* @return array<TUnwrapKey, TUnwrapValue>
*/
public static function unwrap($value)
{
@ -126,9 +153,11 @@ trait EnumeratesValues
/**
* Create a new collection by invoking the callback a given amount of times.
*
* @template TTimesValue
*
* @param int $number
* @param callable|null $callback
* @return static
* @param (callable(int): TTimesValue)|null $callback
* @return static<int, TTimesValue>
*/
public static function times($number, callable $callback = null)
{
@ -137,15 +166,15 @@ trait EnumeratesValues
}
return static::range(1, $number)
->when($callback)
->unless($callback == null)
->map($callback);
}
/**
* Alias for the "avg" method.
*
* @param callable|string|null $callback
* @return mixed
* @param (callable(TValue): float|int)|string|null $callback
* @return float|int|null
*/
public function average($callback = null)
{
@ -155,7 +184,7 @@ trait EnumeratesValues
/**
* Alias for the "contains" method.
*
* @param mixed $key
* @param (callable(TValue, TKey): bool)|TValue|string $key
* @param mixed $operator
* @param mixed $value
* @return bool
@ -168,8 +197,8 @@ trait EnumeratesValues
/**
* Determine if an item exists, using strict comparison.
*
* @param mixed $key
* @param mixed $value
* @param (callable(TValue): bool)|TValue|array-key $key
* @param TValue|null $value
* @return bool
*/
public function containsStrict($key, $value = null)
@ -197,7 +226,7 @@ trait EnumeratesValues
* Dump the items and end the script.
*
* @param mixed ...$args
* @return void
* @return never
*/
public function dd(...$args)
{
@ -225,7 +254,7 @@ trait EnumeratesValues
/**
* Execute a callback over each item.
*
* @param callable $callback
* @param callable(TValue, TKey): mixed $callback
* @return $this
*/
public function each(callable $callback)
@ -242,7 +271,7 @@ trait EnumeratesValues
/**
* Execute a callback over each nested chunk of items.
*
* @param callable $callback
* @param callable(...mixed): mixed $callback
* @return static
*/
public function eachSpread(callable $callback)
@ -257,7 +286,7 @@ trait EnumeratesValues
/**
* Determine if all items pass the given truth test.
*
* @param string|callable $key
* @param (callable(TValue, TKey): bool)|TValue|string $key
* @param mixed $operator
* @param mixed $value
* @return bool
@ -285,7 +314,7 @@ trait EnumeratesValues
* @param string $key
* @param mixed $operator
* @param mixed $value
* @return mixed
* @return TValue|null
*/
public function firstWhere($key, $operator = null, $value = null)
{
@ -305,8 +334,10 @@ trait EnumeratesValues
/**
* Run a map over each nested chunk of items.
*
* @param callable $callback
* @return static
* @template TMapSpreadValue
*
* @param callable(mixed): TMapSpreadValue $callback
* @return static<TKey, TMapSpreadValue>
*/
public function mapSpread(callable $callback)
{
@ -322,8 +353,11 @@ trait EnumeratesValues
*
* The callback should return an associative array with a single key/value pair.
*
* @param callable $callback
* @return static
* @template TMapToGroupsKey of array-key
* @template TMapToGroupsValue
*
* @param callable(TValue, TKey): array<TMapToGroupsKey, TMapToGroupsValue> $callback
* @return static<TMapToGroupsKey, static<int, TMapToGroupsValue>>
*/
public function mapToGroups(callable $callback)
{
@ -335,8 +369,8 @@ trait EnumeratesValues
/**
* Map a collection and flatten the result by a single level.
*
* @param callable $callback
* @return static
* @param callable(TValue, TKey): mixed $callback
* @return static<int, mixed>
*/
public function flatMap(callable $callback)
{
@ -346,8 +380,10 @@ trait EnumeratesValues
/**
* Map the values into a new class.
*
* @param string $class
* @return static
* @template TMapIntoValue
*
* @param class-string<TMapIntoValue> $class
* @return static<TKey, TMapIntoValue>
*/
public function mapInto($class)
{
@ -359,8 +395,8 @@ trait EnumeratesValues
/**
* Get the min value of a given key.
*
* @param callable|string|null $callback
* @return mixed
* @param (callable(TValue):mixed)|string|null $callback
* @return TValue
*/
public function min($callback = null)
{
@ -378,8 +414,8 @@ trait EnumeratesValues
/**
* Get the max value of a given key.
*
* @param callable|string|null $callback
* @return mixed
* @param (callable(TValue):mixed)|string|null $callback
* @return TValue
*/
public function max($callback = null)
{
@ -411,10 +447,10 @@ trait EnumeratesValues
/**
* Partition the collection into two arrays using the given callback or key.
*
* @param callable|string $key
* @param mixed $operator
* @param mixed $value
* @return static
* @param (callable(TValue, TKey): bool)|TValue|string $key
* @param TValue|string|null $operator
* @param TValue|null $value
* @return static<int<0, 1>, static<TKey, TValue>>
*/
public function partition($key, $operator = null, $value = null)
{
@ -439,7 +475,7 @@ trait EnumeratesValues
/**
* Get the sum of the given values.
*
* @param callable|string|null $callback
* @param (callable(TValue): mixed)|string|null $callback
* @return mixed
*/
public function sum($callback = null)
@ -453,35 +489,14 @@ trait EnumeratesValues
}, 0);
}
/**
* Apply the callback if the value is truthy.
*
* @param bool|mixed $value
* @param callable|null $callback
* @param callable|null $default
* @return static|mixed
*/
public function when($value, callable $callback = null, callable $default = null)
{
if (! $callback) {
return new HigherOrderWhenProxy($this, $value);
}
if ($value) {
return $callback($this, $value);
} elseif ($default) {
return $default($this, $value);
}
return $this;
}
/**
* Apply the callback if the collection is empty.
*
* @param callable $callback
* @param callable|null $default
* @return static|mixed
* @template TWhenEmptyReturnType
*
* @param (callable($this): TWhenEmptyReturnType) $callback
* @param (callable($this): TWhenEmptyReturnType)|null $default
* @return $this|TWhenEmptyReturnType
*/
public function whenEmpty(callable $callback, callable $default = null)
{
@ -491,34 +506,25 @@ trait EnumeratesValues
/**
* Apply the callback if the collection is not empty.
*
* @param callable $callback
* @param callable|null $default
* @return static|mixed
* @template TWhenNotEmptyReturnType
*
* @param callable($this): TWhenNotEmptyReturnType $callback
* @param (callable($this): TWhenNotEmptyReturnType)|null $default
* @return $this|TWhenNotEmptyReturnType
*/
public function whenNotEmpty(callable $callback, callable $default = null)
{
return $this->when($this->isNotEmpty(), $callback, $default);
}
/**
* Apply the callback if the value is falsy.
*
* @param bool $value
* @param callable $callback
* @param callable|null $default
* @return static|mixed
*/
public function unless($value, callable $callback, callable $default = null)
{
return $this->when(! $value, $callback, $default);
}
/**
* Apply the callback unless the collection is empty.
*
* @param callable $callback
* @param callable|null $default
* @return static|mixed
* @template TUnlessEmptyReturnType
*
* @param callable($this): TUnlessEmptyReturnType $callback
* @param (callable($this): TUnlessEmptyReturnType)|null $default
* @return $this|TUnlessEmptyReturnType
*/
public function unlessEmpty(callable $callback, callable $default = null)
{
@ -528,9 +534,11 @@ trait EnumeratesValues
/**
* Apply the callback unless the collection is not empty.
*
* @param callable $callback
* @param callable|null $default
* @return static|mixed
* @template TUnlessNotEmptyReturnType
*
* @param callable($this): TUnlessNotEmptyReturnType $callback
* @param (callable($this): TUnlessNotEmptyReturnType)|null $default
* @return $this|TUnlessNotEmptyReturnType
*/
public function unlessNotEmpty(callable $callback, callable $default = null)
{
@ -588,7 +596,7 @@ trait EnumeratesValues
* Filter items by the given key value pair.
*
* @param string $key
* @param mixed $values
* @param \Illuminate\Contracts\Support\Arrayable|iterable $values
* @param bool $strict
* @return static
*/
@ -605,7 +613,7 @@ trait EnumeratesValues
* Filter items by the given key value pair using strict comparison.
*
* @param string $key
* @param mixed $values
* @param \Illuminate\Contracts\Support\Arrayable|iterable $values
* @return static
*/
public function whereInStrict($key, $values)
@ -617,7 +625,7 @@ trait EnumeratesValues
* Filter items such that the value of the given key is between the given values.
*
* @param string $key
* @param array $values
* @param \Illuminate\Contracts\Support\Arrayable|iterable $values
* @return static
*/
public function whereBetween($key, $values)
@ -629,7 +637,7 @@ trait EnumeratesValues
* Filter items such that the value of the given key is not between the given values.
*
* @param string $key
* @param array $values
* @param \Illuminate\Contracts\Support\Arrayable|iterable $values
* @return static
*/
public function whereNotBetween($key, $values)
@ -643,7 +651,7 @@ trait EnumeratesValues
* Filter items by the given key value pair.
*
* @param string $key
* @param mixed $values
* @param \Illuminate\Contracts\Support\Arrayable|iterable $values
* @param bool $strict
* @return static
*/
@ -660,7 +668,7 @@ trait EnumeratesValues
* Filter items by the given key value pair using strict comparison.
*
* @param string $key
* @param mixed $values
* @param \Illuminate\Contracts\Support\Arrayable|iterable $values
* @return static
*/
public function whereNotInStrict($key, $values)
@ -671,7 +679,7 @@ trait EnumeratesValues
/**
* Filter the items, removing any items that don't match the given type(s).
*
* @param string|string[] $type
* @param class-string|array<array-key, class-string> $type
* @return static
*/
public function whereInstanceOf($type)
@ -694,8 +702,10 @@ trait EnumeratesValues
/**
* Pass the collection to the given callback and return the result.
*
* @param callable $callback
* @return mixed
* @template TPipeReturnType
*
* @param callable($this): TPipeReturnType $callback
* @return TPipeReturnType
*/
public function pipe(callable $callback)
{
@ -705,7 +715,7 @@ trait EnumeratesValues
/**
* Pass the collection into a new class.
*
* @param string $class
* @param class-string $class
* @return mixed
*/
public function pipeInto($class)
@ -714,24 +724,30 @@ trait EnumeratesValues
}
/**
* Pass the collection to the given callback and then return it.
* Pass the collection through a series of callable pipes and return the result.
*
* @param callable $callback
* @return $this
* @param array<callable> $callbacks
* @return mixed
*/
public function tap(callable $callback)
public function pipeThrough($callbacks)
{
$callback(clone $this);
return $this;
return Collection::make($callbacks)->reduce(
function ($carry, $callback) {
return $callback($carry);
},
$this,
);
}
/**
* Reduce the collection to a single value.
*
* @param callable $callback
* @param mixed $initial
* @return mixed
* @template TReduceInitial
* @template TReduceReturnType
*
* @param callable(TReduceInitial|TReduceReturnType, TValue, TKey): TReduceReturnType $callback
* @param TReduceInitial $initial
* @return TReduceReturnType
*/
public function reduce(callable $callback, $initial = null)
{
@ -745,21 +761,36 @@ trait EnumeratesValues
}
/**
* Reduce an associative collection to a single value.
* Reduce the collection to multiple aggregate values.
*
* @param callable $callback
* @param mixed $initial
* @return mixed
* @param mixed ...$initial
* @return array
*
* @throws \UnexpectedValueException
*/
public function reduceWithKeys(callable $callback, $initial = null)
public function reduceSpread(callable $callback, ...$initial)
{
return $this->reduce($callback, $initial);
$result = $initial;
foreach ($this as $key => $value) {
$result = call_user_func_array($callback, array_merge($result, [$value, $key]));
if (! is_array($result)) {
throw new UnexpectedValueException(sprintf(
"%s::reduceMany expects reducer to return an array, but got a '%s' instead.",
class_basename(static::class), gettype($result)
));
}
}
return $result;
}
/**
* Create a collection of all elements that do not pass a given truth test.
*
* @param callable|mixed $callback
* @param (callable(TValue, TKey): bool)|bool $callback
* @return static
*/
public function reject($callback = true)
@ -773,10 +804,23 @@ trait EnumeratesValues
});
}
/**
* Pass the collection to the given callback and then return it.
*
* @param callable($this): mixed $callback
* @return $this
*/
public function tap(callable $callback)
{
$callback($this);
return $this;
}
/**
* Return only unique items from the collection array.
*
* @param string|callable|null $key
* @param (callable(TValue, TKey): mixed)|string|null $key
* @param bool $strict
* @return static
*/
@ -798,7 +842,7 @@ trait EnumeratesValues
/**
* Return only unique items from the collection array using strict comparison.
*
* @param string|callable|null $key
* @param (callable(TValue, TKey): mixed)|string|null $key
* @return static
*/
public function uniqueStrict($key = null)
@ -809,7 +853,7 @@ trait EnumeratesValues
/**
* Collect the values into a collection.
*
* @return \Illuminate\Support\Collection
* @return \Illuminate\Support\Collection<TKey, TValue>
*/
public function collect()
{
@ -819,7 +863,7 @@ trait EnumeratesValues
/**
* Get the collection of items as a plain array.
*
* @return array
* @return array<TKey, mixed>
*/
public function toArray()
{
@ -831,10 +875,9 @@ trait EnumeratesValues
/**
* Convert the object into something JSON serializable.
*
* @return array
* @return array<TKey, mixed>
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
public function jsonSerialize(): array
{
return array_map(function ($value) {
if ($value instanceof JsonSerializable) {
@ -878,7 +921,22 @@ trait EnumeratesValues
*/
public function __toString()
{
return $this->toJson();
return $this->escapeWhenCastingToString
? e($this->toJson())
: $this->toJson();
}
/**
* Indicate that the model's string representation should be escaped when __toString is invoked.
*
* @param bool $escape
* @return $this
*/
public function escapeWhenCastingToString($escape = true)
{
$this->escapeWhenCastingToString = $escape;
return $this;
}
/**
@ -913,7 +971,7 @@ trait EnumeratesValues
* Results array of items from Collection or Arrayable.
*
* @param mixed $items
* @return array
* @return array<TKey, TValue>
*/
protected function getArrayableItems($items)
{
@ -1015,7 +1073,7 @@ trait EnumeratesValues
* Make a function to check an item's equality.
*
* @param mixed $value
* @return \Closure
* @return \Closure(mixed): bool
*/
protected function equality($value)
{
@ -1040,7 +1098,7 @@ trait EnumeratesValues
/**
* Make a function that returns what's passed to it.
*
* @return \Closure
* @return \Closure(TValue): TValue
*/
protected function identity()
{

View file

@ -14,9 +14,10 @@
}
],
"require": {
"php": "^7.3|^8.0",
"illuminate/contracts": "^8.0",
"illuminate/macroable": "^8.0"
"php": "^8.0.2",
"illuminate/conditionable": "^9.0",
"illuminate/contracts": "^9.0",
"illuminate/macroable": "^9.0"
},
"autoload": {
"psr-4": {
@ -28,11 +29,11 @@
},
"extra": {
"branch-alias": {
"dev-master": "8.x-dev"
"dev-master": "9.x-dev"
}
},
"suggest": {
"symfony/var-dumper": "Required to use the dump method (^5.1.4)."
"symfony/var-dumper": "Required to use the dump method (^6.0)."
},
"config": {
"sort-packages": true

View file

@ -7,8 +7,11 @@ if (! function_exists('collect')) {
/**
* Create a collection from the given value.
*
* @param mixed $value
* @return \Illuminate\Support\Collection
* @template TKey of array-key
* @template TValue
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue>|null $value
* @return \Illuminate\Support\Collection<TKey, TValue>
*/
function collect($value = null)
{
@ -58,7 +61,7 @@ if (! function_exists('data_get')) {
if ($segment === '*') {
if ($target instanceof Collection) {
$target = $target->all();
} elseif (! is_array($target)) {
} elseif (! is_iterable($target)) {
return value($default);
}

View file

@ -2,17 +2,14 @@
namespace Illuminate\Support;
/**
* @mixin \Illuminate\Support\Enumerable
*/
class HigherOrderWhenProxy
{
/**
* The collection being operated on.
* The target being conditionally operated on.
*
* @var \Illuminate\Support\Enumerable
* @var mixed
*/
protected $collection;
protected $target;
/**
* The condition for proxying.
@ -24,18 +21,18 @@ class HigherOrderWhenProxy
/**
* Create a new proxy instance.
*
* @param \Illuminate\Support\Enumerable $collection
* @param mixed $target
* @param bool $condition
* @return void
*/
public function __construct(Enumerable $collection, $condition)
public function __construct($target, $condition)
{
$this->target = $target;
$this->condition = $condition;
$this->collection = $collection;
}
/**
* Proxy accessing an attribute onto the collection.
* Proxy accessing an attribute onto the target.
*
* @param string $key
* @return mixed
@ -43,12 +40,12 @@ class HigherOrderWhenProxy
public function __get($key)
{
return $this->condition
? $this->collection->{$key}
: $this->collection;
? $this->target->{$key}
: $this->target;
}
/**
* Proxy a method call onto the collection.
* Proxy a method call on the target.
*
* @param string $method
* @param array $parameters
@ -57,7 +54,7 @@ class HigherOrderWhenProxy
public function __call($method, $parameters)
{
return $this->condition
? $this->collection->{$method}(...$parameters)
: $this->collection;
? $this->target->{$method}(...$parameters)
: $this->target;
}
}

View file

@ -1,14 +1,16 @@
Copyright (c) 2018-2019 Fabien Potencier
The MIT License (MIT)
Copyright (c) Taylor Otwell
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:
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 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,

View file

@ -0,0 +1,65 @@
<?php
namespace Illuminate\Support\Traits;
use Closure;
use Illuminate\Support\HigherOrderWhenProxy;
trait Conditionable
{
/**
* Apply the callback if the given "value" is (or resolves to) truthy.
*
* @template TWhenParameter
* @template TWhenReturnType
*
* @param (\Closure($this): TWhenParameter)|TWhenParameter $value
* @param (callable($this, TWhenParameter): TWhenReturnType)|null $callback
* @param (callable($this, TWhenParameter): TWhenReturnType)|null $default
* @return $this|TWhenReturnType
*/
public function when($value, callable $callback = null, callable $default = null)
{
$value = $value instanceof Closure ? $value($this) : $value;
if (! $callback) {
return new HigherOrderWhenProxy($this, $value);
}
if ($value) {
return $callback($this, $value) ?? $this;
} elseif ($default) {
return $default($this, $value) ?? $this;
}
return $this;
}
/**
* Apply the callback if the given "value" is (or resolves to) falsy.
*
* @template TUnlessParameter
* @template TUnlessReturnType
*
* @param (\Closure($this): TUnlessParameter)|TUnlessParameter $value
* @param (callable($this, TUnlessParameter): TUnlessReturnType)|null $callback
* @param (callable($this, TUnlessParameter): TUnlessReturnType)|null $default
* @return $this|TUnlessReturnType
*/
public function unless($value, callable $callback = null, callable $default = null)
{
$value = $value instanceof Closure ? $value($this) : $value;
if (! $callback) {
return new HigherOrderWhenProxy($this, ! $value);
}
if (! $value) {
return $callback($this, $value) ?? $this;
} elseif ($default) {
return $default($this, $value) ?? $this;
}
return $this;
}
}

View file

@ -0,0 +1,33 @@
{
"name": "illuminate/conditionable",
"description": "The Illuminate Conditionable package.",
"license": "MIT",
"homepage": "https://laravel.com",
"support": {
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"require": {
"php": "^8.0.2"
},
"autoload": {
"psr-4": {
"Illuminate\\Support\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "9.x-dev"
}
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}

View file

@ -137,7 +137,7 @@ class BoundMethod
*/
protected static function getCallReflector($callback)
{
if (is_string($callback) && strpos($callback, '::') !== false) {
if (is_string($callback) && str_contains($callback, '::')) {
$callback = explode('::', $callback);
} elseif (is_object($callback) && ! $callback instanceof Closure) {
$callback = [$callback, '__invoke'];
@ -171,6 +171,12 @@ class BoundMethod
$dependencies[] = $parameters[$className];
unset($parameters[$className]);
} elseif ($parameter->isVariadic()) {
$variadicDependencies = $container->make($className);
$dependencies = array_merge($dependencies, is_array($variadicDependencies)
? $variadicDependencies
: [$variadicDependencies]);
} else {
$dependencies[] = $container->make($className);
}
@ -191,6 +197,6 @@ class BoundMethod
*/
protected static function isCallableWithAtSign($callback)
{
return is_string($callback) && strpos($callback, '@') !== false;
return is_string($callback) && str_contains($callback, '@');
}
}

View file

@ -188,8 +188,10 @@ class Container implements ArrayAccess, ContainerContract
/**
* {@inheritdoc}
*
* @return bool
*/
public function has($id)
public function has(string $id): bool
{
return $this->bound($id);
}
@ -607,7 +609,7 @@ class Container implements ArrayAccess, ContainerContract
$instance = $this->make($abstract);
foreach ($this->getReboundCallbacks($abstract) as $callback) {
call_user_func($callback, $this, $instance);
$callback($this, $instance);
}
}
@ -694,8 +696,10 @@ class Container implements ArrayAccess, ContainerContract
/**
* {@inheritdoc}
*
* @return mixed
*/
public function get($id)
public function get(string $id)
{
try {
return $this->resolve($id);
@ -882,10 +886,6 @@ class Container implements ArrayAccess, ContainerContract
return $this->notInstantiable($concrete);
}
// if (in_array($concrete, $this->buildStack)) {
// throw new CircularDependencyException("Circular dependency detected while resolving [{$concrete}].");
// }
$this->buildStack[] = $concrete;
$constructor = $reflector->getConstructor();
@ -1001,7 +1001,7 @@ class Container implements ArrayAccess, ContainerContract
protected function resolvePrimitive(ReflectionParameter $parameter)
{
if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->getName()))) {
return $concrete instanceof Closure ? $concrete($this) : $concrete;
return Util::unwrapIfClosure($concrete, $this);
}
if ($parameter->isDefaultValueAvailable()) {
@ -1237,7 +1237,6 @@ class Container implements ArrayAccess, ContainerContract
* @param string $abstract
* @param object $object
* @param array $callbacksPerType
*
* @return array
*/
protected function getCallbacksForType($abstract, $object, array $callbacksPerType)
@ -1402,8 +1401,7 @@ class Container implements ArrayAccess, ContainerContract
* @param string $key
* @return bool
*/
#[\ReturnTypeWillChange]
public function offsetExists($key)
public function offsetExists($key): bool
{
return $this->bound($key);
}
@ -1414,8 +1412,7 @@ class Container implements ArrayAccess, ContainerContract
* @param string $key
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($key)
public function offsetGet($key): mixed
{
return $this->make($key);
}
@ -1427,8 +1424,7 @@ class Container implements ArrayAccess, ContainerContract
* @param mixed $value
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetSet($key, $value)
public function offsetSet($key, $value): void
{
$this->bind($key, $value instanceof Closure ? $value : function () use ($value) {
return $value;
@ -1441,8 +1437,7 @@ class Container implements ArrayAccess, ContainerContract
* @param string $key
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetUnset($key)
public function offsetUnset($key): void
{
unset($this->bindings[$key], $this->instances[$key], $this->resolved[$key]);
}

View file

@ -86,7 +86,7 @@ class ContextualBindingBuilder implements ContextualBindingBuilderContract
* Specify the configuration item to bind as a primitive.
*
* @param string $key
* @param ?string $default
* @param mixed $default
* @return void
*/
public function giveConfig($key, $default = null)

View file

@ -4,6 +4,7 @@ namespace Illuminate\Container;
use Countable;
use IteratorAggregate;
use Traversable;
class RewindableGenerator implements Countable, IteratorAggregate
{
@ -37,10 +38,9 @@ class RewindableGenerator implements Countable, IteratorAggregate
/**
* Get an iterator from the generator.
*
* @return mixed
* @return \Traversable
*/
#[\ReturnTypeWillChange]
public function getIterator()
public function getIterator(): Traversable
{
return ($this->generator)();
}
@ -50,8 +50,7 @@ class RewindableGenerator implements Countable, IteratorAggregate
*
* @return int
*/
#[\ReturnTypeWillChange]
public function count()
public function count(): int
{
if (is_callable($count = $this->count)) {
$this->count = $count();

View file

@ -33,11 +33,12 @@ class Util
* From global value() helper in Illuminate\Support.
*
* @param mixed $value
* @param mixed ...$args
* @return mixed
*/
public static function unwrapIfClosure($value)
public static function unwrapIfClosure($value, ...$args)
{
return $value instanceof Closure ? $value() : $value;
return $value instanceof Closure ? $value(...$args) : $value;
}
/**
@ -53,7 +54,7 @@ class Util
$type = $parameter->getType();
if (! $type instanceof ReflectionNamedType || $type->isBuiltin()) {
return;
return null;
}
$name = $type->getName();

View file

@ -14,12 +14,12 @@
}
],
"require": {
"php": "^7.3|^8.0",
"illuminate/contracts": "^8.0",
"psr/container": "^1.0"
"php": "^8.0.2",
"illuminate/contracts": "^9.0",
"psr/container": "^1.1.1|^2.0.1"
},
"provide": {
"psr/container-implementation": "1.0"
"psr/container-implementation": "1.1|2.0"
},
"autoload": {
"psr-4": {
@ -28,7 +28,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "8.x-dev"
"dev-master": "9.x-dev"
}
},
"config": {

View file

@ -40,6 +40,13 @@ interface Guard
*/
public function validate(array $credentials = []);
/**
* Determine if the guard has a user instance.
*
* @return bool
*/
public function hasUser();
/**
* Set the current user.
*

View file

@ -8,7 +8,7 @@ interface PasswordBrokerFactory
* Get a password broker instance by name.
*
* @param string|null $name
* @return mixed
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker($name = null);
}

View file

@ -28,6 +28,8 @@ interface Broadcaster
* @param string $event
* @param array $payload
* @return void
*
* @throws \Illuminate\Broadcasting\BroadcastException
*/
public function broadcast(array $channels, $event, array $payload = []);
}

View file

@ -81,6 +81,24 @@ interface Container extends ContainerInterface
*/
public function singletonIf($abstract, $concrete = null);
/**
* Register a scoped binding in the container.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function scoped($abstract, $concrete = null);
/**
* Register a scoped binding if it hasn't already been registered.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function scopedIf($abstract, $concrete = null);
/**
* "Extend" an abstract type in the container.
*
@ -163,6 +181,15 @@ interface Container extends ContainerInterface
*/
public function resolved($abstract);
/**
* Register a new before resolving callback.
*
* @param \Closure|string $abstract
* @param \Closure|null $callback
* @return void
*/
public function beforeResolving($abstract, Closure $callback = null);
/**
* Register a new resolving callback.
*

View file

@ -27,4 +27,13 @@ interface ContextualBindingBuilder
* @return void
*/
public function giveTagged($tag);
/**
* Specify the configuration item to bind as a primitive.
*
* @param string $key
* @param mixed $default
* @return void
*/
public function giveConfig($key, $default = null);
}

View file

@ -0,0 +1,14 @@
<?php
namespace Illuminate\Contracts\Database\Eloquent;
use Illuminate\Contracts\Database\Query\Builder as BaseContract;
/**
* This interface is intentionally empty and exists to improve IDE support.
*
* @mixin \Illuminate\Database\Eloquent\Builder
*/
interface Builder extends BaseContract
{
}

View file

@ -0,0 +1,12 @@
<?php
namespace Illuminate\Contracts\Database\Query;
/**
* This interface is intentionally empty and exists to improve IDE support.
*
* @mixin \Illuminate\Database\Query\Builder
*/
interface Builder
{
}

View file

@ -41,6 +41,8 @@ interface ExceptionHandler
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @param \Throwable $e
* @return void
*
* @internal This method is not meant to be used or overwritten outside the framework.
*/
public function renderForConsole($output, Throwable $e);
}

View file

@ -25,4 +25,11 @@ interface Encrypter
* @throws \Illuminate\Contracts\Encryption\DecryptException
*/
public function decrypt($payload, $unserialize = true);
/**
* Get the encryption key that the encrypter is currently using.
*
* @return string
*/
public function getKey();
}

View file

@ -1,10 +0,0 @@
<?php
namespace Illuminate\Contracts\Filesystem;
use Exception;
class FileExistsException extends Exception
{
//
}

View file

@ -30,9 +30,7 @@ interface Filesystem
* Get the contents of a file.
*
* @param string $path
* @return string
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
* @return string|null
*/
public function get($path);
@ -41,8 +39,6 @@ interface Filesystem
*
* @param string $path
* @return resource|null The path resource or null on failure.
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function readStream($path);
@ -63,9 +59,6 @@ interface Filesystem
* @param resource $resource
* @param array $options
* @return bool
*
* @throws \InvalidArgumentException If $resource is not a file handle.
* @throws \Illuminate\Contracts\Filesystem\FileExistsException
*/
public function writeStream($path, $resource, array $options = []);

View file

@ -24,7 +24,7 @@ interface Application extends Container
/**
* Get the path to the bootstrap directory.
*
* @param string $path Optionally, a path to append to the bootstrap path
* @param string $path
* @return string
*/
public function bootstrapPath($path = '');
@ -32,7 +32,7 @@ interface Application extends Container
/**
* Get the path to the application configuration files.
*
* @param string $path Optionally, a path to append to the config path
* @param string $path
* @return string
*/
public function configPath($path = '');
@ -40,7 +40,7 @@ interface Application extends Container
/**
* Get the path to the database directory.
*
* @param string $path Optionally, a path to append to the database path
* @param string $path
* @return string
*/
public function databasePath($path = '');
@ -56,9 +56,10 @@ interface Application extends Container
/**
* Get the path to the storage directory.
*
* @param string $path
* @return string
*/
public function storagePath();
public function storagePath($path = '');
/**
* Get or check the current application environment.
@ -82,6 +83,13 @@ interface Application extends Container
*/
public function runningUnitTests();
/**
* Get an instance of the maintenance mode manager implementation.
*
* @return \Illuminate\Contracts\Foundation\MaintenanceMode
*/
public function maintenanceMode();
/**
* Determine if the application is currently down for maintenance.
*
@ -206,6 +214,14 @@ interface Application extends Container
*/
public function shouldSkipMiddleware();
/**
* Register a terminating callback with the application.
*
* @param callable|string $callback
* @return \Illuminate\Contracts\Foundation\Application
*/
public function terminating($callback);
/**
* Terminate the application.
*

View file

@ -0,0 +1,14 @@
<?php
namespace Illuminate\Contracts\Foundation;
interface ExceptionRenderer
{
/**
* Renders the given exception as HTML.
*
* @param \Throwable $throwable
* @return string
*/
public function render($throwable);
}

View file

@ -0,0 +1,35 @@
<?php
namespace Illuminate\Contracts\Foundation;
interface MaintenanceMode
{
/**
* Take the application down for maintenance.
*
* @param array $payload
* @return void
*/
public function activate(array $payload): void;
/**
* Take the application out of maintenance.
*
* @return void
*/
public function deactivate(): void;
/**
* Determine if the application is currently down for maintenance.
*
* @return bool
*/
public function active(): bool;
/**
* Get the data array which was provided when the application was placed into maintenance.
*
* @return array
*/
public function data(): array;
}

View file

@ -23,7 +23,7 @@ interface Mailable
public function queue(Queue $queue);
/**
* Deliver the queued message after the given delay.
* Deliver the queued message after (n) seconds.
*
* @param \DateTimeInterface|\DateInterval|int $delay
* @param \Illuminate\Contracts\Queue\Factory $queue

View file

@ -38,11 +38,4 @@ interface Mailer
* @return void
*/
public function send($view, array $data = [], $callback = null);
/**
* Get the array of failed recipients.
*
* @return array
*/
public function failures();
}

View file

@ -33,9 +33,7 @@ interface Job
public function fire();
/**
* Release the job back into the queue.
*
* Accepts a delay specified in seconds.
* Release the job back into the queue after (n) seconds.
*
* @param int $delay
* @return void

View file

@ -43,7 +43,7 @@ interface Queue
public function pushRaw($payload, $queue = null, array $options = []);
/**
* Push a new job onto the queue after a delay.
* Push a new job onto the queue after (n) seconds.
*
* @param \DateTimeInterface|\DateInterval|int $delay
* @param string|object $job
@ -54,7 +54,7 @@ interface Queue
public function later($delay, $job, $data = '', $queue = null);
/**
* Push a new job onto the queue after a delay.
* Push a new job onto a specific queue after (n) seconds.
*
* @param string $queue
* @param \DateTimeInterface|\DateInterval|int $delay

View file

@ -14,14 +14,14 @@ interface QueueableCollection
/**
* Get the identifiers for all of the entities.
*
* @return array
* @return array<int, mixed>
*/
public function getQueueableIds();
/**
* Get the relationships of the entities being queued.
*
* @return array
* @return array<int, string>
*/
public function getQueueableRelations();

View file

@ -7,7 +7,7 @@ interface ResponseFactory
/**
* Create a new response instance.
*
* @param string $content
* @param array|string $content
* @param int $status
* @param array $headers
* @return \Illuminate\Http\Response

View file

@ -69,6 +69,13 @@ interface UrlGenerator
*/
public function action($action, $parameters = [], $absolute = true);
/**
* Get the root controller namespace.
*
* @return string
*/
public function getRootControllerNamespace();
/**
* Set the root controller namespace.
*

View file

@ -0,0 +1,8 @@
<?php
namespace Illuminate\Contracts\Session\Middleware;
interface AuthenticatesSessions
{
//
}

View file

@ -2,12 +2,16 @@
namespace Illuminate\Contracts\Support;
/**
* @template TKey of array-key
* @template TValue
*/
interface Arrayable
{
/**
* Get the instance as an array.
*
* @return array
* @return array<TKey, TValue>
*/
public function toArray();
}

View file

@ -0,0 +1,14 @@
<?php
namespace Illuminate\Contracts\Support;
interface CanBeEscapedWhenCastToString
{
/**
* Indicate that the object's string representation should be escaped when __toString is invoked.
*
* @param bool $escape
* @return $this
*/
public function escapeWhenCastingToString($escape = true);
}

View file

@ -14,9 +14,9 @@
}
],
"require": {
"php": "^7.3|^8.0",
"psr/container": "^1.0",
"psr/simple-cache": "^1.0"
"php": "^8.0.2",
"psr/container": "^1.1.1|^2.0.1",
"psr/simple-cache": "^1.0|^2.0|^3.0"
},
"autoload": {
"psr-4": {
@ -25,7 +25,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "8.x-dev"
"dev-master": "9.x-dev"
}
},
"config": {

View file

@ -0,0 +1,29 @@
<?php
namespace Illuminate\Database;
use RuntimeException;
class ClassMorphViolationException extends RuntimeException
{
/**
* The name of the affected Eloquent model.
*
* @var string
*/
public $model;
/**
* Create a new exception instance.
*
* @param object $model
*/
public function __construct($model)
{
$class = get_class($model);
parent::__construct("No morph map defined for model [{$class}].");
$this->model = $class;
}
}

View file

@ -5,6 +5,7 @@ namespace Illuminate\Database\Concerns;
use Illuminate\Container\Container;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\MultipleRecordsFoundException;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\RecordsNotFoundException;
use Illuminate\Pagination\Cursor;
use Illuminate\Pagination\CursorPaginator;
@ -12,6 +13,7 @@ use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
use Illuminate\Support\LazyCollection;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Conditionable;
use InvalidArgumentException;
use RuntimeException;
@ -111,9 +113,9 @@ trait BuildsQueries
*/
public function chunkById($count, callable $callback, $column = null, $alias = null)
{
$column = $column ?? $this->defaultKeyName();
$column ??= $this->defaultKeyName();
$alias = $alias ?? $column;
$alias ??= $column;
$lastId = null;
@ -210,7 +212,7 @@ trait BuildsQueries
/**
* Query lazily, by chunking the results of a query by comparing IDs.
*
* @param int $count
* @param int $chunkSize
* @param string|null $column
* @param string|null $alias
* @return \Illuminate\Support\LazyCollection
@ -218,22 +220,57 @@ trait BuildsQueries
* @throws \InvalidArgumentException
*/
public function lazyById($chunkSize = 1000, $column = null, $alias = null)
{
return $this->orderedLazyById($chunkSize, $column, $alias);
}
/**
* Query lazily, by chunking the results of a query by comparing IDs in descending order.
*
* @param int $chunkSize
* @param string|null $column
* @param string|null $alias
* @return \Illuminate\Support\LazyCollection
*
* @throws \InvalidArgumentException
*/
public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null)
{
return $this->orderedLazyById($chunkSize, $column, $alias, true);
}
/**
* Query lazily, by chunking the results of a query by comparing IDs in a given order.
*
* @param int $chunkSize
* @param string|null $column
* @param string|null $alias
* @param bool $descending
* @return \Illuminate\Support\LazyCollection
*
* @throws \InvalidArgumentException
*/
protected function orderedLazyById($chunkSize = 1000, $column = null, $alias = null, $descending = false)
{
if ($chunkSize < 1) {
throw new InvalidArgumentException('The chunk size should be at least 1');
}
$column = $column ?? $this->defaultKeyName();
$column ??= $this->defaultKeyName();
$alias = $alias ?? $column;
$alias ??= $column;
return LazyCollection::make(function () use ($chunkSize, $column, $alias) {
return LazyCollection::make(function () use ($chunkSize, $column, $alias, $descending) {
$lastId = null;
while (true) {
$clone = clone $this;
if ($descending) {
$results = $clone->forPageBeforeId($chunkSize, $lastId, $column)->get();
} else {
$results = $clone->forPageAfterId($chunkSize, $lastId, $column)->get();
}
foreach ($results as $result) {
yield $result;
@ -272,12 +309,14 @@ trait BuildsQueries
{
$result = $this->take(2)->get($columns);
if ($result->isEmpty()) {
$count = $result->count();
if ($count === 0) {
throw new RecordsNotFoundException;
}
if ($result->count() > 1) {
throw new MultipleRecordsFoundException;
if ($count > 1) {
throw new MultipleRecordsFoundException($count);
}
return $result->first();
@ -305,8 +344,10 @@ trait BuildsQueries
if (! is_null($cursor)) {
$addCursorConditions = function (self $builder, $previousColumn, $i) use (&$addCursorConditions, $cursor, $orders) {
if (! is_null($previousColumn)) {
$originalColumn = $this->getOriginalColumnNameForCursorPagination($this, $previousColumn);
$builder->where(
$this->getOriginalColumnNameForCursorPagination($this, $previousColumn),
Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn,
'=',
$cursor->parameter($previousColumn)
);
@ -315,8 +356,10 @@ trait BuildsQueries
$builder->where(function (self $builder) use ($addCursorConditions, $cursor, $orders, $i) {
['column' => $column, 'direction' => $direction] = $orders[$i];
$originalColumn = $this->getOriginalColumnNameForCursorPagination($this, $column);
$builder->where(
$this->getOriginalColumnNameForCursorPagination($this, $column),
Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn,
$direction === 'asc' ? '>' : '<',
$cursor->parameter($column)
);
@ -359,7 +402,7 @@ trait BuildsQueries
[$original, $alias] = explode($as, $column);
if ($parameter === $alias) {
if ($parameter === $alias || $builder->getGrammar()->wrap($parameter) === $alias) {
return $original;
}
}
@ -422,10 +465,12 @@ trait BuildsQueries
* Pass the query to a given callback.
*
* @param callable $callback
* @return $this|mixed
* @return $this
*/
public function tap($callback)
{
return $this->when(true, $callback);
$callback($this);
return $this;
}
}

View file

@ -48,7 +48,7 @@ trait ManagesTransactions
$this->transactions = max(0, $this->transactions - 1);
if ($this->transactions == 0) {
optional($this->transactionsManager)->commit($this->getName());
$this->transactionsManager?->commit($this->getName());
}
} catch (Throwable $e) {
$this->handleCommitTransactionException(
@ -83,7 +83,7 @@ trait ManagesTransactions
$this->transactions > 1) {
$this->transactions--;
optional($this->transactionsManager)->rollback(
$this->transactionsManager?->rollback(
$this->getName(), $this->transactions
);
@ -116,7 +116,7 @@ trait ManagesTransactions
$this->transactions++;
optional($this->transactionsManager)->begin(
$this->transactionsManager?->begin(
$this->getName(), $this->transactions
);
@ -194,7 +194,7 @@ trait ManagesTransactions
$this->transactions = max(0, $this->transactions - 1);
if ($this->transactions == 0) {
optional($this->transactionsManager)->commit($this->getName());
$this->transactionsManager?->commit($this->getName());
}
$this->fireConnectionEvent('committed');
@ -258,7 +258,7 @@ trait ManagesTransactions
$this->transactions = $toLevel;
optional($this->transactionsManager)->rollback(
$this->transactionsManager?->rollback(
$this->getName(), $this->transactions
);
@ -297,7 +297,7 @@ trait ManagesTransactions
if ($this->causedByLostConnection($e)) {
$this->transactions = 0;
optional($this->transactionsManager)->rollback(
$this->transactionsManager?->rollback(
$this->getName(), $this->transactions
);
}

View file

@ -0,0 +1,29 @@
<?php
namespace Illuminate\Database\Concerns;
trait ParsesSearchPath
{
/**
* Parse the Postgres "search_path" configuration value into an array.
*
* @param string|array|null $searchPath
* @return array
*/
protected function parseSearchPath($searchPath)
{
if (is_string($searchPath)) {
preg_match_all('/[^\s,"\']+/', $searchPath, $matches);
$searchPath = $matches[0];
}
$searchPath ??= [];
array_walk($searchPath, static function (&$schema) {
$schema = trim($schema, '\'"');
});
return $searchPath;
}
}

View file

@ -5,6 +5,7 @@ namespace Illuminate\Database;
use Closure;
use DateTimeInterface;
use Doctrine\DBAL\Connection as DoctrineConnection;
use Doctrine\DBAL\Types\Type;
use Exception;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Events\QueryExecuted;
@ -21,6 +22,7 @@ use Illuminate\Support\Arr;
use LogicException;
use PDO;
use PDOStatement;
use RuntimeException;
class Connection implements ConnectionInterface
{
@ -54,7 +56,7 @@ class Connection implements ConnectionInterface
*
* @var string|null
*/
protected $type;
protected $readWriteType;
/**
* The table prefix for the connection.
@ -161,6 +163,13 @@ class Connection implements ConnectionInterface
*/
protected $pretending = false;
/**
* All of the callbacks that should be invoked before a query is executed.
*
* @var array
*/
protected $beforeExecutingCallbacks = [];
/**
* The instance of Doctrine connection.
*
@ -168,6 +177,13 @@ class Connection implements ConnectionInterface
*/
protected $doctrineConnection;
/**
* Type mappings that should be registered with new Doctrine connections.
*
* @var array
*/
protected $doctrineTypeMappings = [];
/**
* The connection resolvers.
*
@ -600,7 +616,11 @@ class Connection implements ConnectionInterface
$statement->bindValue(
is_string($key) ? $key : $key + 1,
$value,
is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR
match (true) {
is_int($value) => PDO::PARAM_INT,
is_resource($value) => PDO::PARAM_LOB,
default => PDO::PARAM_STR
},
);
}
}
@ -641,6 +661,10 @@ class Connection implements ConnectionInterface
*/
protected function run($query, $bindings, Closure $callback)
{
foreach ($this->beforeExecutingCallbacks as $beforeExecutingCallback) {
$beforeExecutingCallback($query, $bindings, $this);
}
$this->reconnectIfMissingConnection();
$start = microtime(true);
@ -807,6 +831,19 @@ class Connection implements ConnectionInterface
$this->setPdo(null)->setReadPdo(null);
}
/**
* Register a hook to be run just before a database query is executed.
*
* @param \Closure $callback
* @return $this
*/
public function beforeExecuting(Closure $callback)
{
$this->beforeExecutingCallbacks[] = $callback;
return $this;
}
/**
* Register a database query listener with the connection.
*
@ -832,14 +869,12 @@ class Connection implements ConnectionInterface
return;
}
switch ($event) {
case 'beganTransaction':
return $this->events->dispatch(new TransactionBeginning($this));
case 'committed':
return $this->events->dispatch(new TransactionCommitted($this));
case 'rollingBack':
return $this->events->dispatch(new TransactionRolledBack($this));
}
return $this->events->dispatch(match ($event) {
'beganTransaction' => new TransactionBeginning($this),
'committed' => new TransactionCommitted($this),
'rollingBack' => new TransactionRolledBack($this),
default => null,
});
}
/**
@ -978,14 +1013,46 @@ class Connection implements ConnectionInterface
$this->doctrineConnection = new DoctrineConnection(array_filter([
'pdo' => $this->getPdo(),
'dbname' => $this->getDatabaseName(),
'driver' => method_exists($driver, 'getName') ? $driver->getName() : null,
'driver' => $driver->getName(),
'serverVersion' => $this->getConfig('server_version'),
]), $driver);
foreach ($this->doctrineTypeMappings as $name => $type) {
$this->doctrineConnection
->getDatabasePlatform()
->registerDoctrineTypeMapping($type, $name);
}
}
return $this->doctrineConnection;
}
/**
* Register a custom Doctrine mapping type.
*
* @param string $class
* @param string $name
* @param string $type
* @return void
*
* @throws \Doctrine\DBAL\DBALException
* @throws \RuntimeException
*/
public function registerDoctrineType(string $class, string $name, string $type): void
{
if (! $this->isDoctrineAvailable()) {
throw new RuntimeException(
'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).'
);
}
if (! Type::hasType($name)) {
Type::addType($name, $class);
}
$this->doctrineTypeMappings[$name] = $type;
}
/**
* Get the current PDO connection.
*

View file

@ -241,18 +241,13 @@ class ConnectionFactory
return $this->container->make($key);
}
switch ($config['driver']) {
case 'mysql':
return new MySqlConnector;
case 'pgsql':
return new PostgresConnector;
case 'sqlite':
return new SQLiteConnector;
case 'sqlsrv':
return new SqlServerConnector;
}
throw new InvalidArgumentException("Unsupported driver [{$config['driver']}].");
return match ($config['driver']) {
'mysql' => new MySqlConnector,
'pgsql' => new PostgresConnector,
'sqlite' => new SQLiteConnector,
'sqlsrv' => new SqlServerConnector,
default => throw new InvalidArgumentException("Unsupported driver [{$config['driver']}]."),
};
}
/**
@ -273,17 +268,12 @@ class ConnectionFactory
return $resolver($connection, $database, $prefix, $config);
}
switch ($driver) {
case 'mysql':
return new MySqlConnection($connection, $database, $prefix, $config);
case 'pgsql':
return new PostgresConnection($connection, $database, $prefix, $config);
case 'sqlite':
return new SQLiteConnection($connection, $database, $prefix, $config);
case 'sqlsrv':
return new SqlServerConnection($connection, $database, $prefix, $config);
}
throw new InvalidArgumentException("Unsupported driver [{$driver}].");
return match ($driver) {
'mysql' => new MySqlConnection($connection, $database, $prefix, $config),
'pgsql' => new PostgresConnection($connection, $database, $prefix, $config),
'sqlite' => new SQLiteConnection($connection, $database, $prefix, $config),
'sqlsrv' => new SqlServerConnection($connection, $database, $prefix, $config),
default => throw new InvalidArgumentException("Unsupported driver [{$driver}]."),
};
}
}

View file

@ -2,10 +2,13 @@
namespace Illuminate\Database\Connectors;
use Illuminate\Database\Concerns\ParsesSearchPath;
use PDO;
class PostgresConnector extends Connector implements ConnectorInterface
{
use ParsesSearchPath;
/**
* The default PDO connection options.
*
@ -33,6 +36,8 @@ class PostgresConnector extends Connector implements ConnectorInterface
$this->getDsn($config), $config, $this->getOptions($config)
);
$this->configureIsolationLevel($connection, $config);
$this->configureEncoding($connection, $config);
// Next, we will check to see if a timezone has been specified in this config
@ -40,7 +45,7 @@ class PostgresConnector extends Connector implements ConnectorInterface
// database. Setting this DB timezone is an optional configuration item.
$this->configureTimezone($connection, $config);
$this->configureSchema($connection, $config);
$this->configureSearchPath($connection, $config);
// Postgres allows an application_name to be set by the user and this name is
// used to when monitoring the application with pg_stat_activity. So we'll
@ -52,6 +57,20 @@ class PostgresConnector extends Connector implements ConnectorInterface
return $connection;
}
/**
* Set the connection transaction isolation level.
*
* @param \PDO $connection
* @param array $config
* @return void
*/
protected function configureIsolationLevel($connection, array $config)
{
if (isset($config['isolation_level'])) {
$connection->prepare("set session characteristics as transaction isolation level {$config['isolation_level']}")->execute();
}
}
/**
* Set the connection character set and collation.
*
@ -85,38 +104,36 @@ class PostgresConnector extends Connector implements ConnectorInterface
}
/**
* Set the schema on the connection.
* Set the "search_path" on the database connection.
*
* @param \PDO $connection
* @param array $config
* @return void
*/
protected function configureSchema($connection, $config)
protected function configureSearchPath($connection, $config)
{
if (isset($config['schema'])) {
$schema = $this->formatSchema($config['schema']);
if (isset($config['search_path']) || isset($config['schema'])) {
$searchPath = $this->quoteSearchPath(
$this->parseSearchPath($config['search_path'] ?? $config['schema'])
);
$connection->prepare("set search_path to {$schema}")->execute();
$connection->prepare("set search_path to {$searchPath}")->execute();
}
}
/**
* Format the schema for the DSN.
* Format the search path for the DSN.
*
* @param array|string $schema
* @param array $searchPath
* @return string
*/
protected function formatSchema($schema)
protected function quoteSearchPath($searchPath)
{
if (is_array($schema)) {
return '"'.implode('", "', $schema).'"';
}
return '"'.$schema.'"';
return count($searchPath) === 1 ? '"'.$searchPath[0].'"' : '"'.implode('", "', $searchPath).'"';
}
/**
* Set the schema on the connection.
* Set the application name on the connection.
*
* @param \PDO $connection
* @param array $config
@ -146,7 +163,7 @@ class PostgresConnector extends Connector implements ConnectorInterface
$host = isset($host) ? "host={$host};" : '';
$dsn = "pgsql:{$host}dbname={$database}";
$dsn = "pgsql:{$host}dbname='{$database}'";
// If a port was specified, we will add it to this Postgres DSN connections
// format. Once we have done that we are ready to return this connection

View file

@ -67,8 +67,16 @@ class DbCommand extends Command
}
if ($this->option('read')) {
if (is_array($connection['read']['host'])) {
$connection['read']['host'] = $connection['read']['host'][0];
}
$connection = array_merge($connection, $connection['read']);
} elseif ($this->option('write')) {
if (is_array($connection['write']['host'])) {
$connection['write']['host'] = $connection['write']['host'][0];
}
$connection = array_merge($connection, $connection['write']);
}
@ -135,8 +143,8 @@ class DbCommand extends Command
'--user='.$connection['username'],
], $this->getOptionalArguments([
'password' => '--password='.$connection['password'],
'unix_socket' => '--socket='.$connection['unix_socket'],
'charset' => '--default-character-set='.$connection['charset'],
'unix_socket' => '--socket='.($connection['unix_socket'] ?? ''),
'charset' => '--default-character-set='.($connection['charset'] ?? ''),
], $connection), [$connection['database']]);
}

View file

@ -22,6 +22,15 @@ class DumpCommand extends Command
{--path= : The path where the schema dump file should be stored}
{--prune : Delete all existing migration files}';
/**
* The name of the console command.
*
* This name is used to identify the command during lazy loading.
*
* @var string|null
*/
protected static $defaultName = 'schema:dump';
/**
* The console command description.
*

View file

@ -15,6 +15,15 @@ class FactoryMakeCommand extends GeneratorCommand
*/
protected $name = 'make:factory';
/**
* The name of the console command.
*
* This name is used to identify the command during lazy loading.
*
* @var string|null
*/
protected static $defaultName = 'make:factory';
/**
* The console command description.
*
@ -112,7 +121,7 @@ class FactoryMakeCommand extends GeneratorCommand
*/
protected function guessModelName($name)
{
if (Str::endsWith($name, 'Factory')) {
if (str_ends_with($name, 'Factory')) {
$name = substr($name, 0, -7);
}

View file

@ -3,21 +3,16 @@
namespace {{ factoryNamespace }};
use Illuminate\Database\Eloquent\Factories\Factory;
use {{ namespacedModel }};
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\{{ namespacedModel }}>
*/
class {{ factory }}Factory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = {{ model }}::class;
/**
* Define the model's default state.
*
* @return array
* @return array<string, mixed>
*/
public function definition()
{

View file

@ -6,8 +6,10 @@ use Illuminate\Console\Command;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Eloquent\MassPrunable;
use Illuminate\Database\Eloquent\Prunable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Events\ModelsPruned;
use Illuminate\Support\Str;
use InvalidArgumentException;
use Symfony\Component\Finder\Finder;
class PruneCommand extends Command
@ -19,7 +21,9 @@ class PruneCommand extends Command
*/
protected $signature = 'model:prune
{--model=* : Class names of the models to be pruned}
{--chunk=1000 : The number of models to retrieve per chunk of models to be deleted}';
{--except=* : Class names of the models to be excluded from pruning}
{--chunk=1000 : The number of models to retrieve per chunk of models to be deleted}
{--pretend : Display the number of prunable records found instead of deleting them}';
/**
* The console command description.
@ -44,6 +48,14 @@ class PruneCommand extends Command
return;
}
if ($this->option('pretend')) {
$models->each(function ($model) {
$this->pretendToPrune($model);
});
return;
}
$events->listen(ModelsPruned::class, function ($event) {
$this->info("{$event->count} [{$event->model}] records have been pruned.");
});
@ -78,7 +90,13 @@ class PruneCommand extends Command
return collect($models);
}
return collect((new Finder)->in(app_path('Models'))->files())
$except = $this->option('except');
if (! empty($models) && ! empty($except)) {
throw new InvalidArgumentException('The --models and --except options cannot be combined.');
}
return collect((new Finder)->in($this->getDefaultPath())->files()->name('*.php'))
->map(function ($model) {
$namespace = $this->laravel->getNamespace();
@ -87,11 +105,25 @@ class PruneCommand extends Command
['\\', ''],
Str::after($model->getRealPath(), realpath(app_path()).DIRECTORY_SEPARATOR)
);
})->when(! empty($except), function ($models) use ($except) {
return $models->reject(function ($model) use ($except) {
return in_array($model, $except);
});
})->filter(function ($model) {
return $this->isPrunable($model);
})->values();
}
/**
* Get the default path where models are located.
*
* @return string
*/
protected function getDefaultPath()
{
return app_path('Models');
}
/**
* Determine if the given model class is prunable.
*
@ -104,4 +136,26 @@ class PruneCommand extends Command
return in_array(Prunable::class, $uses) || in_array(MassPrunable::class, $uses);
}
/**
* Display how many models will be pruned.
*
* @param string $model
* @return void
*/
protected function pretendToPrune($model)
{
$instance = new $model;
$count = $instance->prunable()
->when(in_array(SoftDeletes::class, class_uses_recursive(get_class($instance))), function ($query) {
$query->withTrashed();
})->count();
if ($count === 0) {
$this->info("No prunable [$model] records found.");
} else {
$this->info("{$count} [{$model}] records will be pruned.");
}
}
}

View file

@ -20,6 +20,15 @@ class SeedCommand extends Command
*/
protected $name = 'db:seed';
/**
* The name of the console command.
*
* This name is used to identify the command during lazy loading.
*
* @var string|null
*/
protected static $defaultName = 'db:seed';
/**
* The console command description.
*
@ -84,7 +93,7 @@ class SeedCommand extends Command
{
$class = $this->input->getArgument('class') ?? $this->input->getOption('class');
if (strpos($class, '\\') === false) {
if (! str_contains($class, '\\')) {
$class = 'Database\\Seeders\\'.$class;
}

View file

@ -13,6 +13,15 @@ class SeederMakeCommand extends GeneratorCommand
*/
protected $name = 'make:seeder';
/**
* The name of the console command.
*
* This name is used to identify the command during lazy loading.
*
* @var string|null
*/
protected static $defaultName = 'make:seeder';
/**
* The console command description.
*

View file

@ -0,0 +1,19 @@
<?php
namespace Illuminate\Database\Console\Seeds;
use Illuminate\Database\Eloquent\Model;
trait WithoutModelEvents
{
/**
* Prevent model events from being dispatched by the given callback.
*
* @param callable $callback
* @return callable
*/
public function withoutModelEvents(callable $callback)
{
return fn () => Model::withoutEvents($callback);
}
}

View file

@ -2,6 +2,7 @@
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class {{ class }} extends Seeder

View file

@ -17,6 +17,15 @@ class WipeCommand extends Command
*/
protected $name = 'db:wipe';
/**
* The name of the console command.
*
* This name is used to identify the command during lazy loading.
*
* @var string|null
*/
protected static $defaultName = 'db:wipe';
/**
* The console command description.
*

View file

@ -2,39 +2,29 @@
namespace Illuminate\Database\DBAL;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Exception as DBALException;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\PhpDateTimeMappingType;
use Doctrine\DBAL\Types\Type;
class TimestampType extends Type
class TimestampType extends Type implements PhpDateTimeMappingType
{
/**
* {@inheritdoc}
*/
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
$name = $platform->getName();
switch ($name) {
case 'mysql':
case 'mysql2':
return $this->getMySqlPlatformSQLDeclaration($fieldDeclaration);
case 'postgresql':
case 'pgsql':
case 'postgres':
return $this->getPostgresPlatformSQLDeclaration($fieldDeclaration);
case 'mssql':
return $this->getSqlServerPlatformSQLDeclaration($fieldDeclaration);
case 'sqlite':
case 'sqlite3':
return $this->getSQLitePlatformSQLDeclaration($fieldDeclaration);
default:
throw new DBALException('Invalid platform: '.$name);
}
return match ($name = $platform->getName()) {
'mysql',
'mysql2' => $this->getMySqlPlatformSQLDeclaration($fieldDeclaration),
'postgresql',
'pgsql',
'postgres' => $this->getPostgresPlatformSQLDeclaration($fieldDeclaration),
'mssql' => $this->getSqlServerPlatformSQLDeclaration($fieldDeclaration),
'sqlite',
'sqlite3' => $this->getSQLitePlatformSQLDeclaration($fieldDeclaration),
default => throw new DBALException('Invalid platform: '.$name),
};
}
/**

View file

@ -2,12 +2,14 @@
namespace Illuminate\Database;
use Doctrine\DBAL\Types\Type;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Support\Arr;
use Illuminate\Support\ConfigurationUrlParser;
use Illuminate\Support\Str;
use InvalidArgumentException;
use PDO;
use RuntimeException;
/**
* @mixin \Illuminate\Database\Connection
@ -49,6 +51,13 @@ class DatabaseManager implements ConnectionResolverInterface
*/
protected $reconnector;
/**
* The custom Doctrine column types.
*
* @var array
*/
protected $doctrineTypes = [];
/**
* Create a new database manager instance.
*
@ -183,6 +192,8 @@ class DatabaseManager implements ConnectionResolverInterface
// the connection, which will allow us to reconnect from the connections.
$connection->setReconnector($this->reconnector);
$this->registerConfiguredDoctrineTypes($connection);
return $connection;
}
@ -204,6 +215,49 @@ class DatabaseManager implements ConnectionResolverInterface
return $connection;
}
/**
* Register custom Doctrine types with the connection.
*
* @param \Illuminate\Database\Connection $connection
* @return void
*/
protected function registerConfiguredDoctrineTypes(Connection $connection): void
{
foreach ($this->app['config']->get('database.dbal.types', []) as $name => $class) {
$this->registerDoctrineType($class, $name, $name);
}
foreach ($this->doctrineTypes as $name => [$type, $class]) {
$connection->registerDoctrineType($class, $name, $type);
}
}
/**
* Register a custom Doctrine type.
*
* @param string $class
* @param string $name
* @param string $type
* @return void
*
* @throws \Doctrine\DBAL\DBALException
* @throws \RuntimeException
*/
public function registerDoctrineType(string $class, string $name, string $type): void
{
if (! class_exists('Doctrine\DBAL\Connection')) {
throw new RuntimeException(
'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).'
);
}
if (! Type::hasType($name)) {
Type::addType($name, $class);
}
$this->doctrineTypes[$name] = [$type, $class];
}
/**
* Disconnect from the given database and remove from local cache.
*
@ -342,6 +396,17 @@ class DatabaseManager implements ConnectionResolverInterface
$this->extensions[$name] = $resolver;
}
/**
* Remove an extension connection resolver.
*
* @param string $name
* @return void
*/
public function forgetExtension($name)
{
unset($this->extensions[$name]);
}
/**
* Return all of the created connections.
*

View file

@ -2,7 +2,6 @@
namespace Illuminate\Database;
use Doctrine\DBAL\Types\Type;
use Faker\Factory as FakerFactory;
use Faker\Generator as FakerGenerator;
use Illuminate\Contracts\Queue\EntityResolver;
@ -44,7 +43,6 @@ class DatabaseServiceProvider extends ServiceProvider
$this->registerConnectionServices();
$this->registerEloquentFactory();
$this->registerQueueableEntityResolver();
$this->registerDoctrineTypes();
}
/**
@ -72,6 +70,10 @@ class DatabaseServiceProvider extends ServiceProvider
return $app['db']->connection();
});
$this->app->bind('db.schema', function ($app) {
return $app['db']->connection()->getSchemaBuilder();
});
$this->app->singleton('db.transactions', function ($app) {
return new DatabaseTransactionsManager;
});
@ -108,24 +110,4 @@ class DatabaseServiceProvider extends ServiceProvider
return new QueueEntityResolver;
});
}
/**
* Register custom types with the Doctrine DBAL library.
*
* @return void
*/
protected function registerDoctrineTypes()
{
if (! class_exists(Type::class)) {
return;
}
$types = $this->app['config']->get('database.dbal.types', []);
foreach ($types as $name => $class) {
if (! Type::hasType($name)) {
Type::addType($name, $class);
}
}
}
}

View file

@ -57,7 +57,7 @@ class DatabaseTransactionRecord
public function executeCallbacks()
{
foreach ($this->callbacks as $callback) {
call_user_func($callback);
$callback();
}
}

View file

@ -81,7 +81,7 @@ class DatabaseTransactionsManager
return $current->addCallback($callback);
}
call_user_func($callback);
$callback();
}
/**

View file

@ -16,7 +16,7 @@ trait DetectsConcurrencyErrors
*/
protected function causedByConcurrencyError(Throwable $e)
{
if ($e instanceof PDOException && $e->getCode() === '40001') {
if ($e instanceof PDOException && ($e->getCode() === 40001 || $e->getCode() === '40001')) {
return true;
}

View file

@ -54,6 +54,9 @@ trait DetectsLostConnections
'SQLSTATE[08S01]: Communication link failure',
'SQLSTATE[08006] [7] could not connect to server: Connection refused Is the server running on host',
'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: No route to host',
'The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior.',
'SQLSTATE[08006] [7] could not translate host name',
'TCP Provider: Error code 0x274C',
]);
}
}

View file

@ -5,9 +5,10 @@ namespace Illuminate\Database\Eloquent;
use BadMethodCallException;
use Closure;
use Exception;
use Illuminate\Contracts\Database\Eloquent\Builder as BuilderContract;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Concerns\BuildsQueries;
use Illuminate\Database\Concerns\ExplainsQueries;
use Illuminate\Database\Eloquent\Concerns\QueriesRelationships;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Query\Builder as QueryBuilder;
@ -24,11 +25,10 @@ use ReflectionMethod;
*
* @mixin \Illuminate\Database\Query\Builder
*/
class Builder
class Builder implements BuilderContract
{
use Concerns\QueriesRelationships, ExplainsQueries, ForwardsCalls;
use BuildsQueries {
sole as baseSole;
use BuildsQueries, ForwardsCalls, QueriesRelationships {
BuildsQueries::sole as baseSole;
}
/**
@ -73,12 +73,22 @@ class Builder
*/
protected $onDelete;
/**
* The properties that should be returned from query builder.
*
* @var string[]
*/
protected $propertyPassthru = [
'from',
];
/**
* The methods that should be returned from query builder.
*
* @var string[]
*/
protected $passthru = [
'aggregate',
'average',
'avg',
'count',
@ -86,6 +96,7 @@ class Builder
'doesntExist',
'dump',
'exists',
'explain',
'getBindings',
'getConnection',
'getGrammar',
@ -285,7 +296,7 @@ class Builder
*/
public function firstWhere($column, $operator = null, $value = null, $boolean = 'and')
{
return $this->where($column, $operator, $value, $boolean)->first();
return $this->where(...func_get_args())->first();
}
/**
@ -305,6 +316,33 @@ class Builder
return $this->where($column, $operator, $value, 'or');
}
/**
* Add a basic "where not" clause to the query.
*
* @param \Closure|string|array|\Illuminate\Database\Query\Expression $column
* @param mixed $operator
* @param mixed $value
* @param string $boolean
* @return $this
*/
public function whereNot($column, $operator = null, $value = null, $boolean = 'and')
{
return $this->where($column, $operator, $value, $boolean.' not');
}
/**
* Add an "or where not" clause to the query.
*
* @param \Closure|array|string|\Illuminate\Database\Query\Expression $column
* @param mixed $operator
* @param mixed $value
* @return $this
*/
public function orWhereNot($column, $operator = null, $value = null)
{
return $this->whereNot($column, $operator, $value, 'or');
}
/**
* Add an "order by" clause for a timestamp to the query.
*
@ -415,7 +453,7 @@ class Builder
* @param array $columns
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static|static[]
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
*/
public function findOrFail($id, $columns = ['*'])
{
@ -424,18 +462,24 @@ class Builder
$id = $id instanceof Arrayable ? $id->toArray() : $id;
if (is_array($id)) {
if (count($result) === count(array_unique($id))) {
return $result;
if (count($result) !== count(array_unique($id))) {
throw (new ModelNotFoundException)->setModel(
get_class($this->model), array_diff($id, $result->modelKeys())
);
}
} elseif (! is_null($result)) {
return $result;
}
if (is_null($result)) {
throw (new ModelNotFoundException)->setModel(
get_class($this->model), $id
);
}
return $result;
}
/**
* Find a model by its primary key or return fresh model instance.
*
@ -506,7 +550,7 @@ class Builder
* @param array $columns
* @return \Illuminate\Database\Eloquent\Model|static
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
*/
public function firstOrFail($columns = ['*'])
{
@ -545,7 +589,7 @@ class Builder
* @param array|string $columns
* @return \Illuminate\Database\Eloquent\Model
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
* @throws \Illuminate\Database\MultipleRecordsFoundException
*/
public function sole($columns = ['*'])
@ -570,6 +614,33 @@ class Builder
}
}
/**
* Get a single column's value from the first result of a query if it's the sole matching record.
*
* @param string|\Illuminate\Database\Query\Expression $column
* @return mixed
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
* @throws \Illuminate\Database\MultipleRecordsFoundException
*/
public function soleValue($column)
{
return $this->sole([$column])->{Str::afterLast($column, '.')};
}
/**
* Get a single column's value from the first result of the query or throw an exception.
*
* @param string|\Illuminate\Database\Query\Expression $column
* @return mixed
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
*/
public function valueOrFail($column)
{
return $this->firstOrFail([$column])->{Str::afterLast($column, '.')};
}
/**
* Execute the query as a "select" statement.
*
@ -615,7 +686,7 @@ class Builder
// For nested eager loads we'll skip loading them here and they will be set as an
// eager load on the query to retrieve the relation so that they will be eager
// loaded on that query, because that is where they get hydrated as models.
if (strpos($name, '.') === false) {
if (! str_contains($name, '.')) {
$models = $this->eagerLoadRelation($models, $name, $constraints);
}
}
@ -713,7 +784,7 @@ class Builder
*/
protected function isNestedUnder($relation, $name)
{
return Str::contains($name, '.') && Str::startsWith($name, $relation.'.');
return str_contains($name, '.') && str_starts_with($name, $relation.'.');
}
/**
@ -842,9 +913,7 @@ class Builder
*/
protected function ensureOrderForCursorPagination($shouldReverse = false)
{
$orders = collect($this->query->orders);
if ($orders->count() === 0) {
if (empty($this->query->orders) && empty($this->query->unionOrders)) {
$this->enforceOrderBy();
}
@ -856,6 +925,10 @@ class Builder
})->toArray();
}
if ($this->query->unionOrders) {
return collect($this->query->unionOrders);
}
return collect($this->query->orders);
}
@ -979,7 +1052,7 @@ class Builder
$qualifiedColumn = end($segments).'.'.$column;
$values[$qualifiedColumn] = $values[$column];
$values[$qualifiedColumn] = Arr::get($values, $qualifiedColumn, $values[$column]);
unset($values[$column]);
@ -1336,7 +1409,7 @@ class Builder
if (is_numeric($name)) {
$name = $constraints;
[$name, $constraints] = Str::contains($name, ':')
[$name, $constraints] = str_contains($name, ':')
? $this->createSelectWithConstraint($name)
: [$name, static function () {
//
@ -1364,7 +1437,7 @@ class Builder
{
return [explode(':', $name)[0], static function ($query) use ($name) {
$query->select(array_map(static function ($column) use ($query) {
if (Str::contains($column, '.')) {
if (str_contains($column, '.')) {
return $column;
}
@ -1586,6 +1659,10 @@ class Builder
return new HigherOrderBuilderProxy($this, $key);
}
if (in_array($key, $this->propertyPassthru)) {
return $this->toBase()->{$key};
}
throw new Exception("Property [{$key}] does not exist on the Eloquent builder instance.");
}

View file

@ -33,8 +33,7 @@ class ArrayObject extends BaseArrayObject implements Arrayable, JsonSerializable
*
* @return array
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
public function jsonSerialize(): array
{
return $this->getArrayCopy();
}

View file

@ -20,17 +20,25 @@ class AsEncryptedArrayObject implements Castable
{
public function get($model, $key, $value, $attributes)
{
if (isset($attributes[$key])) {
return new ArrayObject(json_decode(Crypt::decryptString($attributes[$key]), true));
}
return null;
}
public function set($model, $key, $value, $attributes)
{
if (! is_null($value)) {
return [$key => Crypt::encryptString(json_encode($value))];
}
return null;
}
public function serialize($model, string $key, $value, array $attributes)
{
return $value->getArrayCopy();
return ! is_null($value) ? $value->getArrayCopy() : null;
}
};
}

View file

@ -21,13 +21,21 @@ class AsEncryptedCollection implements Castable
{
public function get($model, $key, $value, $attributes)
{
if (isset($attributes[$key])) {
return new Collection(json_decode(Crypt::decryptString($attributes[$key]), true));
}
return null;
}
public function set($model, $key, $value, $attributes)
{
if (! is_null($value)) {
return [$key => Crypt::encryptString(json_encode($value))];
}
return null;
}
};
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Illuminate\Database\Eloquent\Casts;
use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Support\Str;
class AsStringable implements Castable
{
/**
* Get the caster class to use when casting from / to this cast target.
*
* @param array $arguments
* @return object|string
*/
public static function castUsing(array $arguments)
{
return new class implements CastsAttributes
{
public function get($model, $key, $value, $attributes)
{
return isset($value) ? Str::of($value) : null;
}
public function set($model, $key, $value, $attributes)
{
return isset($value) ? (string) $value : null;
}
};
}
}

View file

@ -0,0 +1,86 @@
<?php
namespace Illuminate\Database\Eloquent\Casts;
class Attribute
{
/**
* The attribute accessor.
*
* @var callable
*/
public $get;
/**
* The attribute mutator.
*
* @var callable
*/
public $set;
/**
* Indicates if caching of objects is enabled for this attribute.
*
* @var bool
*/
public $withObjectCaching = true;
/**
* Create a new attribute accessor / mutator.
*
* @param callable|null $get
* @param callable|null $set
* @return void
*/
public function __construct(callable $get = null, callable $set = null)
{
$this->get = $get;
$this->set = $set;
}
/**
* Create a new attribute accessor / mutator.
*
* @param callable|null $get
* @param callable|null $set
* @return static
*/
public static function make(callable $get = null, callable $set = null): static
{
return new static($get, $set);
}
/**
* Create a new attribute accessor.
*
* @param callable $get
* @return static
*/
public static function get(callable $get)
{
return new static($get);
}
/**
* Create a new attribute mutator.
*
* @param callable $set
* @return static
*/
public static function set(callable $set)
{
return new static(null, $set);
}
/**
* Disable object caching for the attribute.
*
* @return static
*/
public function withoutObjectCaching()
{
$this->withObjectCaching = false;
return $this;
}
}

View file

@ -7,17 +7,24 @@ use Illuminate\Contracts\Queue\QueueableEntity;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection as BaseCollection;
use Illuminate\Support\Str;
use LogicException;
/**
* @template TKey of array-key
* @template TModel of \Illuminate\Database\Eloquent\Model
*
* @extends \Illuminate\Support\Collection<TKey, TModel>
*/
class Collection extends BaseCollection implements QueueableCollection
{
/**
* Find a model in the collection by key.
*
* @template TFindDefault
*
* @param mixed $key
* @param mixed $default
* @return \Illuminate\Database\Eloquent\Model|static|null
* @param TFindDefault $default
* @return static<TKey|TModel>|TModel|TFindDefault
*/
public function find($key, $default = null)
{
@ -45,7 +52,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Load a set of relationships onto the collection.
*
* @param array|string $relations
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
* @return $this
*/
public function load($relations)
@ -66,9 +73,9 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Load a set of aggregations over relationship's column onto the collection.
*
* @param array|string $relations
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
* @param string $column
* @param string $function
* @param string|null $function
* @return $this
*/
public function loadAggregate($relations, $column, $function = null)
@ -92,7 +99,9 @@ class Collection extends BaseCollection implements QueueableCollection
$this->each(function ($model) use ($models, $attributes) {
$extraAttributes = Arr::only($models->get($model->getKey())->getAttributes(), $attributes);
$model->forceFill($extraAttributes)->syncOriginalAttributes($attributes);
$model->forceFill($extraAttributes)
->syncOriginalAttributes($attributes)
->mergeCasts($models->get($model->getKey())->getCasts());
});
return $this;
@ -101,7 +110,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Load a set of relationship counts onto the collection.
*
* @param array|string $relations
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
* @return $this
*/
public function loadCount($relations)
@ -112,7 +121,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Load a set of relationship's max column values onto the collection.
*
* @param array|string $relations
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
* @param string $column
* @return $this
*/
@ -124,7 +133,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Load a set of relationship's min column values onto the collection.
*
* @param array|string $relations
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
* @param string $column
* @return $this
*/
@ -136,7 +145,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Load a set of relationship's column summations onto the collection.
*
* @param array|string $relations
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
* @param string $column
* @return $this
*/
@ -148,7 +157,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Load a set of relationship's average column values onto the collection.
*
* @param array|string $relations
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
* @param string $column
* @return $this
*/
@ -160,7 +169,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Load a set of related existences onto the collection.
*
* @param array|string $relations
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
* @return $this
*/
public function loadExists($relations)
@ -171,7 +180,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Load a set of relationships onto the collection if they are not already eager loaded.
*
* @param array|string $relations
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
* @return $this
*/
public function loadMissing($relations)
@ -187,7 +196,7 @@ class Collection extends BaseCollection implements QueueableCollection
$segments = explode('.', explode(':', $key)[0]);
if (Str::contains($key, ':')) {
if (str_contains($key, ':')) {
$segments[count($segments) - 1] .= ':'.explode(':', $key)[1];
}
@ -245,7 +254,7 @@ class Collection extends BaseCollection implements QueueableCollection
* Load a set of relationships onto the mixed relationship collection.
*
* @param string $relation
* @param array $relations
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string> $relations
* @return $this
*/
public function loadMorph($relation, $relations)
@ -266,7 +275,7 @@ class Collection extends BaseCollection implements QueueableCollection
* Load a set of relationship counts onto the mixed relationship collection.
*
* @param string $relation
* @param array $relations
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string> $relations
* @return $this
*/
public function loadMorphCount($relation, $relations)
@ -286,7 +295,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Determine if a key exists in the collection.
*
* @param mixed $key
* @param (callable(TModel, TKey): bool)|TModel|string $key
* @param mixed $operator
* @param mixed $value
* @return bool
@ -311,7 +320,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Get the array of primary keys.
*
* @return array
* @return array<int, array-key>
*/
public function modelKeys()
{
@ -323,7 +332,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Merge the collection with the given items.
*
* @param \ArrayAccess|array $items
* @param iterable<array-key, TModel> $items
* @return static
*/
public function merge($items)
@ -340,8 +349,10 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Run a map over each of the items.
*
* @param callable $callback
* @return \Illuminate\Support\Collection|static
* @template TMapValue
*
* @param callable(TModel, TKey): TMapValue $callback
* @return \Illuminate\Support\Collection<TKey, TMapValue>|static<TKey, TMapValue>
*/
public function map(callable $callback)
{
@ -357,8 +368,11 @@ class Collection extends BaseCollection implements QueueableCollection
*
* The callback should return an associative array with a single key / value pair.
*
* @param callable $callback
* @return \Illuminate\Support\Collection|static
* @template TMapWithKeysKey of array-key
* @template TMapWithKeysValue
*
* @param callable(TModel, TKey): array<TMapWithKeysKey, TMapWithKeysValue> $callback
* @return \Illuminate\Support\Collection<TMapWithKeysKey, TMapWithKeysValue>|static<TMapWithKeysKey, TMapWithKeysValue>
*/
public function mapWithKeys(callable $callback)
{
@ -372,7 +386,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Reload a fresh model instance from the database for all the entities.
*
* @param array|string $with
* @param array<array-key, string>|string $with
* @return static
*/
public function fresh($with = [])
@ -400,7 +414,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Diff the collection with the given items.
*
* @param \ArrayAccess|array $items
* @param iterable<array-key, TModel> $items
* @return static
*/
public function diff($items)
@ -421,7 +435,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Intersect the collection with the given items.
*
* @param \ArrayAccess|array $items
* @param iterable<array-key, TModel> $items
* @return static
*/
public function intersect($items)
@ -446,9 +460,9 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Return only unique items from the collection.
*
* @param string|callable|null $key
* @param (callable(TModel, TKey): mixed)|string|null $key
* @param bool $strict
* @return static
* @return static<int, TModel>
*/
public function unique($key = null, $strict = false)
{
@ -462,8 +476,8 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Returns only the models from the collection with the specified keys.
*
* @param mixed $keys
* @return static
* @param array<array-key, mixed>|null $keys
* @return static<int, TModel>
*/
public function only($keys)
{
@ -479,8 +493,8 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Returns all models in the collection except the models with specified keys.
*
* @param mixed $keys
* @return static
* @param array<array-key, mixed>|null $keys
* @return static<int, TModel>
*/
public function except($keys)
{
@ -492,7 +506,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Make the given, typically visible, attributes hidden across the entire collection.
*
* @param array|string $attributes
* @param array<array-key, string>|string $attributes
* @return $this
*/
public function makeHidden($attributes)
@ -503,7 +517,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Make the given, typically hidden, attributes visible across the entire collection.
*
* @param array|string $attributes
* @param array<array-key, string>|string $attributes
* @return $this
*/
public function makeVisible($attributes)
@ -514,7 +528,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Append an attribute across the entire collection.
*
* @param array|string $attributes
* @param array<array-key, string>|string $attributes
* @return $this
*/
public function append($attributes)
@ -525,8 +539,8 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Get a dictionary keyed by primary keys.
*
* @param \ArrayAccess|array|null $items
* @return array
* @param iterable<array-key, TModel>|null $items
* @return array<array-key, TModel>
*/
public function getDictionary($items = null)
{
@ -548,9 +562,9 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Get an array with the values of a given key.
*
* @param string|array $value
* @param string|array<array-key, string> $value
* @param string|null $key
* @return \Illuminate\Support\Collection
* @return \Illuminate\Support\Collection<int, mixed>
*/
public function pluck($value, $key = null)
{
@ -560,7 +574,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Get the keys of the collection items.
*
* @return \Illuminate\Support\Collection
* @return \Illuminate\Support\Collection<int, TKey>
*/
public function keys()
{
@ -570,8 +584,10 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Zip the collection together with one or more arrays.
*
* @param mixed ...$items
* @return \Illuminate\Support\Collection
* @template TZipValue
*
* @param \Illuminate\Contracts\Support\Arrayable<array-key, TZipValue>|iterable<array-key, TZipValue> ...$items
* @return \Illuminate\Support\Collection<int, \Illuminate\Support\Collection<int, TModel|TZipValue>>
*/
public function zip($items)
{
@ -581,7 +597,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Collapse the collection of items into a single array.
*
* @return \Illuminate\Support\Collection
* @return \Illuminate\Support\Collection<int, mixed>
*/
public function collapse()
{
@ -592,7 +608,7 @@ class Collection extends BaseCollection implements QueueableCollection
* Get a flattened array of the items in the collection.
*
* @param int $depth
* @return \Illuminate\Support\Collection
* @return \Illuminate\Support\Collection<int, mixed>
*/
public function flatten($depth = INF)
{
@ -602,7 +618,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Flip the items in the collection.
*
* @return \Illuminate\Support\Collection
* @return \Illuminate\Support\Collection<TModel, TKey>
*/
public function flip()
{
@ -612,9 +628,11 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Pad collection to the specified length with a value.
*
* @template TPadValue
*
* @param int $size
* @param mixed $value
* @return \Illuminate\Support\Collection
* @param TPadValue $value
* @return \Illuminate\Support\Collection<int, TModel|TPadValue>
*/
public function pad($size, $value)
{
@ -625,7 +643,7 @@ class Collection extends BaseCollection implements QueueableCollection
* Get the comparison function to detect duplicates.
*
* @param bool $strict
* @return \Closure
* @return callable(TValue, TValue): bool
*/
protected function duplicateComparator($strict)
{
@ -661,7 +679,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Get the identifiers for all of the entities.
*
* @return array
* @return array<int, mixed>
*/
public function getQueueableIds()
{
@ -677,7 +695,7 @@ class Collection extends BaseCollection implements QueueableCollection
/**
* Get the relationships of the entities being queued.
*
* @return array
* @return array<int, string>
*/
public function getQueueableRelations()
{

View file

@ -2,8 +2,6 @@
namespace Illuminate\Database\Eloquent\Concerns;
use Illuminate\Support\Str;
trait GuardsAttributes
{
/**
@ -187,8 +185,8 @@ trait GuardsAttributes
}
return empty($this->getFillable()) &&
strpos($key, '.') === false &&
! Str::startsWith($key, '_');
! str_contains($key, '.') &&
! str_starts_with($key, '_');
}
/**
@ -217,9 +215,14 @@ trait GuardsAttributes
protected function isGuardableColumn($key)
{
if (! isset(static::$guardableColumns[get_class($this)])) {
static::$guardableColumns[get_class($this)] = $this->getConnection()
$columns = $this->getConnection()
->getSchemaBuilder()
->getColumnListing($this->getTable());
if (empty($columns)) {
return true;
}
static::$guardableColumns[get_class($this)] = $columns;
}
return in_array($key, static::$guardableColumns[get_class($this)]);

View file

@ -4,10 +4,14 @@ namespace Illuminate\Database\Eloquent\Concerns;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use DateTimeImmutable;
use DateTimeInterface;
use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
use Illuminate\Database\Eloquent\Casts\AsCollection;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\InvalidCastException;
use Illuminate\Database\Eloquent\JsonEncodingException;
use Illuminate\Database\Eloquent\Relations\Relation;
@ -20,6 +24,9 @@ use Illuminate\Support\Facades\Date;
use Illuminate\Support\Str;
use InvalidArgumentException;
use LogicException;
use ReflectionClass;
use ReflectionMethod;
use ReflectionNamedType;
trait HasAttributes
{
@ -58,6 +65,13 @@ trait HasAttributes
*/
protected $classCastCache = [];
/**
* The attributes that have been cast using "Attribute" return type mutators.
*
* @var array
*/
protected $attributeCastCache = [];
/**
* The built-in, primitive cast types supported by Eloquent.
*
@ -128,6 +142,27 @@ trait HasAttributes
*/
protected static $mutatorCache = [];
/**
* The cache of the "Attribute" return type marked mutated attributes for each class.
*
* @var array
*/
protected static $attributeMutatorCache = [];
/**
* The cache of the "Attribute" return type marked mutated, gettable attributes for each class.
*
* @var array
*/
protected static $getAttributeMutatorCache = [];
/**
* The cache of the "Attribute" return type marked mutated, settable attributes for each class.
*
* @var array
*/
protected static $setAttributeMutatorCache = [];
/**
* The encrypter instance that is used to encrypt attributes.
*
@ -253,7 +288,7 @@ trait HasAttributes
$attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]);
}
if ($attributes[$key] && $attributes[$key] instanceof DateTimeInterface &&
if ($attributes[$key] instanceof DateTimeInterface &&
$this->isClassCastable($key)) {
$attributes[$key] = $this->serializeDate($attributes[$key]);
}
@ -262,6 +297,10 @@ trait HasAttributes
$attributes[$key] = $this->serializeClassCastableAttribute($key, $attributes[$key]);
}
if ($this->isEnumCastable($key) && (! ($attributes[$key] ?? null) instanceof Arrayable)) {
$attributes[$key] = isset($attributes[$key]) ? $attributes[$key]->value : null;
}
if ($attributes[$key] instanceof Arrayable) {
$attributes[$key] = $attributes[$key]->toArray();
}
@ -387,6 +426,7 @@ trait HasAttributes
if (array_key_exists($key, $this->attributes) ||
array_key_exists($key, $this->casts) ||
$this->hasGetMutator($key) ||
$this->hasAttributeMutator($key) ||
$this->isClassCastable($key)) {
return $this->getAttributeValue($key);
}
@ -460,6 +500,10 @@ trait HasAttributes
*/
public function isRelation($key)
{
if ($this->hasAttributeMutator($key)) {
return false;
}
return method_exists($this, $key) ||
(static::$relationResolvers[get_class($this)][$key] ?? null);
}
@ -519,6 +563,48 @@ trait HasAttributes
return method_exists($this, 'get'.Str::studly($key).'Attribute');
}
/**
* Determine if a "Attribute" return type marked mutator exists for an attribute.
*
* @param string $key
* @return bool
*/
public function hasAttributeMutator($key)
{
if (isset(static::$attributeMutatorCache[get_class($this)][$key])) {
return static::$attributeMutatorCache[get_class($this)][$key];
}
if (! method_exists($this, $method = Str::camel($key))) {
return static::$attributeMutatorCache[get_class($this)][$key] = false;
}
$returnType = (new ReflectionMethod($this, $method))->getReturnType();
return static::$attributeMutatorCache[get_class($this)][$key] =
$returnType instanceof ReflectionNamedType &&
$returnType->getName() === Attribute::class;
}
/**
* Determine if a "Attribute" return type marked get mutator exists for an attribute.
*
* @param string $key
* @return bool
*/
public function hasAttributeGetMutator($key)
{
if (isset(static::$getAttributeMutatorCache[get_class($this)][$key])) {
return static::$getAttributeMutatorCache[get_class($this)][$key];
}
if (! $this->hasAttributeMutator($key)) {
return static::$getAttributeMutatorCache[get_class($this)][$key] = false;
}
return static::$getAttributeMutatorCache[get_class($this)][$key] = is_callable($this->{Str::camel($key)}()->get);
}
/**
* Get the value of an attribute using its mutator.
*
@ -531,6 +617,34 @@ trait HasAttributes
return $this->{'get'.Str::studly($key).'Attribute'}($value);
}
/**
* Get the value of an "Attribute" return type marked attribute using its mutator.
*
* @param string $key
* @param mixed $value
* @return mixed
*/
protected function mutateAttributeMarkedAttribute($key, $value)
{
if (isset($this->attributeCastCache[$key])) {
return $this->attributeCastCache[$key];
}
$attribute = $this->{Str::camel($key)}();
$value = call_user_func($attribute->get ?: function ($value) {
return $value;
}, $value, $this->attributes);
if (! is_object($value) || ! $attribute->withObjectCaching) {
unset($this->attributeCastCache[$key]);
} else {
$this->attributeCastCache[$key] = $value;
}
return $value;
}
/**
* Get the value of an attribute using its mutator for array conversion.
*
@ -540,9 +654,18 @@ trait HasAttributes
*/
protected function mutateAttributeForArray($key, $value)
{
$value = $this->isClassCastable($key)
? $this->getClassCastableAttributeValue($key, $value)
: $this->mutateAttribute($key, $value);
if ($this->isClassCastable($key)) {
$value = $this->getClassCastableAttributeValue($key, $value);
} elseif (isset(static::$getAttributeMutatorCache[get_class($this)][$key]) &&
static::$getAttributeMutatorCache[get_class($this)][$key] === true) {
$value = $this->mutateAttributeMarkedAttribute($key, $value);
$value = $value instanceof DateTimeInterface
? $this->serializeDate($value)
: $value;
} else {
$value = $this->mutateAttribute($key, $value);
}
return $value instanceof Arrayable ? $value->toArray() : $value;
}
@ -620,6 +743,10 @@ trait HasAttributes
return $this->asTimestamp($value);
}
if ($this->isEnumCastable($key)) {
return $this->getEnumCastableAttributeValue($key, $value);
}
if ($this->isClassCastable($key)) {
return $this->getClassCastableAttributeValue($key, $value);
}
@ -655,6 +782,28 @@ trait HasAttributes
}
}
/**
* Cast the given attribute to an enum.
*
* @param string $key
* @param mixed $value
* @return mixed
*/
protected function getEnumCastableAttributeValue($key, $value)
{
if (is_null($value)) {
return;
}
$castType = $this->getCasts()[$key];
if ($value instanceof $castType) {
return $value;
}
return $castType::from($value);
}
/**
* Get the type of cast for a model attribute.
*
@ -715,8 +864,8 @@ trait HasAttributes
*/
protected function isCustomDateTimeCast($cast)
{
return strncmp($cast, 'date:', 5) === 0 ||
strncmp($cast, 'datetime:', 9) === 0;
return str_starts_with($cast, 'date:') ||
str_starts_with($cast, 'datetime:');
}
/**
@ -739,7 +888,7 @@ trait HasAttributes
*/
protected function isDecimalCast($cast)
{
return strncmp($cast, 'decimal:', 8) === 0;
return str_starts_with($cast, 'decimal:');
}
/**
@ -756,6 +905,8 @@ trait HasAttributes
// this model, such as "json_encoding" a listing of data for storage.
if ($this->hasSetMutator($key)) {
return $this->setMutatedAttributeValue($key, $value);
} elseif ($this->hasAttributeSetMutator($key)) {
return $this->setAttributeMarkedMutatedAttributeValue($key, $value);
}
// If an attribute is listed as a "date", we'll convert it from a DateTime
@ -765,6 +916,12 @@ trait HasAttributes
$value = $this->fromDateTime($value);
}
if ($this->isEnumCastable($key)) {
$this->setEnumCastableAttribute($key, $value);
return $this;
}
if ($this->isClassCastable($key)) {
$this->setClassCastableAttribute($key, $value);
@ -778,7 +935,7 @@ trait HasAttributes
// If this attribute contains a JSON ->, we'll set the proper value in the
// attribute's underlying array. This takes care of properly nesting an
// attribute in the array's value in the case of deeply nested items.
if (Str::contains($key, '->')) {
if (str_contains($key, '->')) {
return $this->fillJsonAttribute($key, $value);
}
@ -802,6 +959,32 @@ trait HasAttributes
return method_exists($this, 'set'.Str::studly($key).'Attribute');
}
/**
* Determine if an "Attribute" return type marked set mutator exists for an attribute.
*
* @param string $key
* @return bool
*/
public function hasAttributeSetMutator($key)
{
$class = get_class($this);
if (isset(static::$setAttributeMutatorCache[$class][$key])) {
return static::$setAttributeMutatorCache[$class][$key];
}
if (! method_exists($this, $method = Str::camel($key))) {
return static::$setAttributeMutatorCache[$class][$key] = false;
}
$returnType = (new ReflectionMethod($this, $method))->getReturnType();
return static::$setAttributeMutatorCache[$class][$key] =
$returnType instanceof ReflectionNamedType &&
$returnType->getName() === Attribute::class &&
is_callable($this->{$method}()->set);
}
/**
* Set the value of an attribute using its mutator.
*
@ -814,6 +997,35 @@ trait HasAttributes
return $this->{'set'.Str::studly($key).'Attribute'}($value);
}
/**
* Set the value of a "Attribute" return type marked attribute using its mutator.
*
* @param string $key
* @param mixed $value
* @return mixed
*/
protected function setAttributeMarkedMutatedAttributeValue($key, $value)
{
$attribute = $this->{Str::camel($key)}();
$callback = $attribute->set ?: function ($value) use ($key) {
$this->attributes[$key] = $value;
};
$this->attributes = array_merge(
$this->attributes,
$this->normalizeCastClassResponse(
$key, $callback($value, $this->attributes)
)
);
if (! is_object($value) || ! $attribute->withObjectCaching) {
unset($this->attributeCastCache[$key]);
} else {
$this->attributeCastCache[$key] = $value;
}
}
/**
* Determine if the given attribute is a date or date castable.
*
@ -859,22 +1071,12 @@ trait HasAttributes
{
$caster = $this->resolveCasterClass($key);
if (is_null($value)) {
$this->attributes = array_merge($this->attributes, array_map(
function () {
},
$this->normalizeCastClassResponse($key, $caster->set(
$this, $key, $this->{$key}, $this->attributes
))
));
} else {
$this->attributes = array_merge(
$this->attributes,
$this->normalizeCastClassResponse($key, $caster->set(
$this, $key, $value, $this->attributes
))
);
}
if ($caster instanceof CastsInboundAttributes || ! is_object($value)) {
unset($this->classCastCache[$key]);
@ -883,6 +1085,26 @@ trait HasAttributes
}
}
/**
* Set the value of an enum castable attribute.
*
* @param string $key
* @param \BackedEnum $value
* @return void
*/
protected function setEnumCastableAttribute($key, $value)
{
$enumClass = $this->getCasts()[$key];
if (! isset($value)) {
$this->attributes[$key] = null;
} elseif ($value instanceof $enumClass) {
$this->attributes[$key] = $value->value;
} else {
$this->attributes[$key] = $enumClass::from($value)->value;
}
}
/**
* Get an array attribute with the given key and value set.
*
@ -1002,16 +1224,12 @@ trait HasAttributes
*/
public function fromFloat($value)
{
switch ((string) $value) {
case 'Infinity':
return INF;
case '-Infinity':
return -INF;
case 'NaN':
return NAN;
default:
return (float) $value;
}
return match ((string) $value) {
'Infinity' => INF,
'-Infinity' => -INF,
'NaN' => NAN,
default => (float) $value,
};
}
/**
@ -1132,7 +1350,7 @@ trait HasAttributes
*/
protected function serializeDate(DateTimeInterface $date)
{
return $date instanceof \DateTimeImmutable ?
return $date instanceof DateTimeImmutable ?
CarbonImmutable::instance($date)->toJSON() :
Carbon::instance($date)->toJSON();
}
@ -1220,6 +1438,17 @@ trait HasAttributes
return $this->hasCast($key, ['date', 'datetime', 'immutable_date', 'immutable_datetime']);
}
/**
* Determine whether a value is Date / DateTime custom-castable for inbound manipulation.
*
* @param string $key
* @return bool
*/
protected function isDateCastableWithCustomFormat($key)
{
return $this->hasCast($key, ['custom_datetime', 'immutable_custom_datetime']);
}
/**
* Determine whether a value is JSON castable for inbound manipulation.
*
@ -1269,6 +1498,29 @@ trait HasAttributes
throw new InvalidCastException($this->getModel(), $key, $castType);
}
/**
* Determine if the given key is cast using an enum.
*
* @param string $key
* @return bool
*/
protected function isEnumCastable($key)
{
if (! array_key_exists($key, $this->getCasts())) {
return false;
}
$castType = $this->getCasts()[$key];
if (in_array($castType, static::$primitiveCastTypes)) {
return false;
}
if (function_exists('enum_exists') && enum_exists($castType)) {
return true;
}
}
/**
* Determine if the key is deviable using a custom class.
*
@ -1294,8 +1546,9 @@ trait HasAttributes
*/
protected function isClassSerializable($key)
{
return $this->isClassCastable($key) &&
method_exists($this->parseCasterClass($this->getCasts()[$key]), 'serialize');
return ! $this->isEnumCastable($key) &&
$this->isClassCastable($key) &&
method_exists($this->resolveCasterClass($key), 'serialize');
}
/**
@ -1310,7 +1563,7 @@ trait HasAttributes
$arguments = [];
if (is_string($castType) && strpos($castType, ':') !== false) {
if (is_string($castType) && str_contains($castType, ':')) {
$segments = explode(':', $castType, 2);
$castType = $segments[0];
@ -1336,11 +1589,22 @@ trait HasAttributes
*/
protected function parseCasterClass($class)
{
return strpos($class, ':') === false
return ! str_contains($class, ':')
? $class
: explode(':', $class, 2)[0];
}
/**
* Merge the cast class and attribute cast attributes back into the model.
*
* @return void
*/
protected function mergeAttributesFromCachedCasts()
{
$this->mergeAttributesFromClassCasts();
$this->mergeAttributesFromAttributeCasts();
}
/**
* Merge the cast class attributes back into the model.
*
@ -1360,6 +1624,33 @@ trait HasAttributes
}
}
/**
* Merge the cast class attributes back into the model.
*
* @return void
*/
protected function mergeAttributesFromAttributeCasts()
{
foreach ($this->attributeCastCache as $key => $value) {
$attribute = $this->{Str::camel($key)}();
if ($attribute->get && ! $attribute->set) {
continue;
}
$callback = $attribute->set ?: function ($value) use ($key) {
$this->attributes[$key] = $value;
};
$this->attributes = array_merge(
$this->attributes,
$this->normalizeCastClassResponse(
$key, $callback($value, $this->attributes)
)
);
}
}
/**
* Normalize the response from a custom class caster.
*
@ -1379,7 +1670,7 @@ trait HasAttributes
*/
public function getAttributes()
{
$this->mergeAttributesFromClassCasts();
$this->mergeAttributesFromCachedCasts();
return $this->attributes;
}
@ -1410,6 +1701,7 @@ trait HasAttributes
}
$this->classCastCache = [];
$this->attributeCastCache = [];
return $this;
}
@ -1643,14 +1935,14 @@ trait HasAttributes
return true;
} elseif (is_null($attribute)) {
return false;
} elseif ($this->isDateAttribute($key)) {
} elseif ($this->isDateAttribute($key) || $this->isDateCastableWithCustomFormat($key)) {
return $this->fromDateTime($attribute) ===
$this->fromDateTime($original);
} elseif ($this->hasCast($key, ['object', 'collection'])) {
return $this->castAttribute($key, $attribute) ==
$this->castAttribute($key, $original);
} elseif ($this->hasCast($key, ['real', 'float', 'double'])) {
if (($attribute === null && $original !== null) || ($attribute !== null && $original === null)) {
if ($original === null) {
return false;
}
@ -1658,6 +1950,8 @@ trait HasAttributes
} elseif ($this->hasCast($key, static::$primitiveCastTypes)) {
return $this->castAttribute($key, $attribute) ===
$this->castAttribute($key, $original);
} elseif ($this->isClassCastable($key) && in_array($this->getCasts()[$key], [AsArrayObject::class, AsCollection::class])) {
return $this->fromJson($attribute) === $this->fromJson($original);
}
return is_numeric($attribute) && is_numeric($original)
@ -1678,6 +1972,8 @@ trait HasAttributes
// retrieval from the model to a form that is more useful for usage.
if ($this->hasGetMutator($key)) {
return $this->mutateAttribute($key, $value);
} elseif ($this->hasAttributeGetMutator($key)) {
return $this->mutateAttributeMarkedAttribute($key, $value);
}
// If the attribute exists within the cast array, we will convert it to
@ -1761,7 +2057,15 @@ trait HasAttributes
*/
public static function cacheMutatedAttributes($class)
{
static::$mutatorCache[$class] = collect(static::getMutatorMethods($class))->map(function ($match) {
static::$getAttributeMutatorCache[$class] =
collect($attributeMutatorMethods = static::getAttributeMarkedMutatorMethods($class))
->mapWithKeys(function ($match) {
return [lcfirst(static::$snakeAttributes ? Str::snake($match) : $match) => true];
})->all();
static::$mutatorCache[$class] = collect(static::getMutatorMethods($class))
->merge($attributeMutatorMethods)
->map(function ($match) {
return lcfirst(static::$snakeAttributes ? Str::snake($match) : $match);
})->all();
}
@ -1778,4 +2082,30 @@ trait HasAttributes
return $matches[1];
}
/**
* Get all of the "Attribute" return typed attribute mutator methods.
*
* @param mixed $class
* @return array
*/
protected static function getAttributeMarkedMutatorMethods($class)
{
$instance = is_object($class) ? $class : new $class;
return collect((new ReflectionClass($instance))->getMethods())->filter(function ($method) use ($instance) {
$returnType = $method->getReturnType();
if ($returnType instanceof ReflectionNamedType &&
$returnType->getName() === Attribute::class) {
$method->setAccessible(true);
if (is_callable($method->invoke($instance)->get)) {
return true;
}
}
return false;
})->map->name->values()->all();
}
}

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