diff --git a/.gitignore b/.gitignore index 39397245b7ec..496161c0903d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,11 @@ +.DS_Store +.phpunit.result.cache +/.fleet +/.idea /.phpunit.cache +/phpunit.xml +/.vscode /vendor composer.phar composer.lock -.DS_Store Thumbs.db -/phpunit.xml -/.idea -/.fleet -/.vscode -.phpunit.result.cache diff --git a/CHANGELOG.md b/CHANGELOG.md index 41e5f8e99db1..cb060adf4e73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Release Notes for 12.x -## [Unreleased](https://github.com/laravel/framework/compare/v12.28.0...12.x) +## [Unreleased](https://github.com/laravel/framework/compare/v12.28.1...12.x) + +## [v12.28.1](https://github.com/laravel/framework/compare/v12.28.0...v12.28.1) - 2025-09-04 + +* [12.x] Rename `group` to `messageGroup` property by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/56919 +* Fix PHP_CLI_SERVER_WORKERS inside laravel/sail by [@akyrey](https://github.com/akyrey) in https://github.com/laravel/framework/pull/56923 +* Allow RouteRegistrar to be Macroable by [@moshe-autoleadstar](https://github.com/moshe-autoleadstar) in https://github.com/laravel/framework/pull/56921 +* [12.x] Fix SesV2Transport docblock by [@dwightwatson](https://github.com/dwightwatson) in https://github.com/laravel/framework/pull/56917 +* [12.x] Prevent unnecessary query logging on exceptions with a custom renderer by [@luanfreitasdev](https://github.com/luanfreitasdev) in https://github.com/laravel/framework/pull/56874 +* [12.x] Reduce meaningless intermediate variables by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/framework/pull/56927 ## [v12.28.0](https://github.com/laravel/framework/compare/v12.27.1...v12.28.0) - 2025-09-03 diff --git a/composer.json b/composer.json index ccc5038ef538..8a45d839e515 100644 --- a/composer.json +++ b/composer.json @@ -181,7 +181,7 @@ "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).", "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", - "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "fakerphp/faker": "Required to generate fake data using the fake() helper (^1.23).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", diff --git a/config/cache.php b/config/cache.php index f529e1e3ec74..4c754591211c 100644 --- a/config/cache.php +++ b/config/cache.php @@ -38,6 +38,11 @@ 'serialize' => false, ], + 'session' => [ + 'driver' => 'session', + 'key' => env('SESSION_CACHE_KEY', '_cache'), + ], + 'database' => [ 'driver' => 'database', 'connection' => env('DB_CACHE_CONNECTION'), diff --git a/config/database.php b/config/database.php index 0a68f1fe29f7..1d9883833e62 100644 --- a/config/database.php +++ b/config/database.php @@ -42,6 +42,7 @@ 'journal_mode' => null, 'synchronous' => null, 'transaction_mode' => 'DEFERRED', + 'pragmas' => [], ], 'mysql' => [ diff --git a/src/Illuminate/Auth/Access/Gate.php b/src/Illuminate/Auth/Access/Gate.php index f64f584ffc78..2341efd8109d 100644 --- a/src/Illuminate/Auth/Access/Gate.php +++ b/src/Illuminate/Auth/Access/Gate.php @@ -186,7 +186,7 @@ protected function authorizeOnDemand($condition, $message, $code, $allowWhenResp $response = $condition; } - return with($response instanceof Response ? $response : new Response( + return ($response instanceof Response ? $response : new Response( (bool) $response === $allowWhenResponseIs, $message, $code ))->authorize(); } diff --git a/src/Illuminate/Broadcasting/BroadcastEvent.php b/src/Illuminate/Broadcasting/BroadcastEvent.php index c4da0faab220..77b864e0b6d9 100644 --- a/src/Illuminate/Broadcasting/BroadcastEvent.php +++ b/src/Illuminate/Broadcasting/BroadcastEvent.php @@ -188,7 +188,7 @@ public function middleware(): array /** * Handle a job failure. * - * @param \Throwable $e + * @param \Throwable|null $e * @return void */ public function failed(?Throwable $e = null): void diff --git a/src/Illuminate/Cache/CacheManager.php b/src/Illuminate/Cache/CacheManager.php index 0a0c2de5e171..2e793f6083d2 100755 --- a/src/Illuminate/Cache/CacheManager.php +++ b/src/Illuminate/Cache/CacheManager.php @@ -169,6 +169,82 @@ protected function createArrayDriver(array $config) return $this->repository(new ArrayStore($config['serialize'] ?? false), $config); } + /** + * Create an instance of the database cache driver. + * + * @param array $config + * @return \Illuminate\Cache\Repository + */ + protected function createDatabaseDriver(array $config) + { + $connection = $this->app['db']->connection($config['connection'] ?? null); + + $store = new DatabaseStore( + $connection, + $config['table'], + $this->getPrefix($config), + $config['lock_table'] ?? 'cache_locks', + $config['lock_lottery'] ?? [2, 100], + $config['lock_timeout'] ?? 86400, + ); + + return $this->repository( + $store->setLockConnection( + $this->app['db']->connection($config['lock_connection'] ?? $config['connection'] ?? null) + ), + $config + ); + } + + /** + * Create an instance of the DynamoDB cache driver. + * + * @param array $config + * @return \Illuminate\Cache\Repository + */ + protected function createDynamodbDriver(array $config) + { + $client = $this->newDynamodbClient($config); + + return $this->repository( + new DynamoDbStore( + $client, + $config['table'], + $config['attributes']['key'] ?? 'key', + $config['attributes']['value'] ?? 'value', + $config['attributes']['expiration'] ?? 'expires_at', + $this->getPrefix($config) + ), + $config + ); + } + + /** + * Create new DynamoDb Client instance. + * + * @return \Aws\DynamoDb\DynamoDbClient + */ + protected function newDynamodbClient(array $config) + { + $dynamoConfig = [ + 'region' => $config['region'], + 'version' => 'latest', + 'endpoint' => $config['endpoint'] ?? null, + ]; + + if (! empty($config['key']) && ! empty($config['secret'])) { + $dynamoConfig['credentials'] = Arr::only( + $config, ['key', 'secret'] + ); + + if (! empty($config['token'])) { + $dynamoConfig['credentials']['token'] = $config['token']; + } + } + + return new DynamoDbClient($dynamoConfig); + } + /** * Create an instance of the file cache driver. * @@ -235,79 +311,38 @@ protected function createRedisDriver(array $config) } /** - * Create an instance of the database cache driver. + * Create an instance of the session cache driver. * * @param array $config * @return \Illuminate\Cache\Repository */ - protected function createDatabaseDriver(array $config) + protected function createSessionDriver(array $config) { - $connection = $this->app['db']->connection($config['connection'] ?? null); - - $store = new DatabaseStore( - $connection, - $config['table'], - $this->getPrefix($config), - $config['lock_table'] ?? 'cache_locks', - $config['lock_lottery'] ?? [2, 100], - $config['lock_timeout'] ?? 86400, - ); - return $this->repository( - $store->setLockConnection( - $this->app['db']->connection($config['lock_connection'] ?? $config['connection'] ?? null) + new SessionStore( + $this->getSession(), + $config['key'] ?? '_cache', ), $config ); } /** - * Create an instance of the DynamoDB cache driver. + * Get the session store implementation. * - * @param array $config - * @return \Illuminate\Cache\Repository - */ - protected function createDynamodbDriver(array $config) - { - $client = $this->newDynamodbClient($config); - - return $this->repository( - new DynamoDbStore( - $client, - $config['table'], - $config['attributes']['key'] ?? 'key', - $config['attributes']['value'] ?? 'value', - $config['attributes']['expiration'] ?? 'expires_at', - $this->getPrefix($config) - ), - $config - ); - } - - /** - * Create new DynamoDb Client instance. + * @return \Illuminate\Contracts\Session\Session * - * @return \Aws\DynamoDb\DynamoDbClient + * @throws \InvalidArgumentException */ - protected function newDynamodbClient(array $config) + protected function getSession() { - $dynamoConfig = [ - 'region' => $config['region'], - 'version' => 'latest', - 'endpoint' => $config['endpoint'] ?? null, - ]; - - if (! empty($config['key']) && ! empty($config['secret'])) { - $dynamoConfig['credentials'] = Arr::only( - $config, ['key', 'secret'] - ); + $session = $this->app['session'] ?? null; - if (! empty($config['token'])) { - $dynamoConfig['credentials']['token'] = $config['token']; - } + if (! $session) { + throw new InvalidArgumentException('Session store requires session manager to be available in container.'); } - return new DynamoDbClient($dynamoConfig); + return $session; } /** diff --git a/src/Illuminate/Cache/RedisStore.php b/src/Illuminate/Cache/RedisStore.php index a7bbfa1b79e5..4f18dcf5d1b9 100755 --- a/src/Illuminate/Cache/RedisStore.php +++ b/src/Illuminate/Cache/RedisStore.php @@ -462,7 +462,7 @@ protected function serialize($value) */ protected function shouldBeStoredWithoutSerialization($value): bool { - return is_numeric($value) && ! in_array($value, [INF, -INF]) && ! is_nan($value); + return is_numeric($value) && is_finite($value); } /** diff --git a/src/Illuminate/Cache/RedisTagSet.php b/src/Illuminate/Cache/RedisTagSet.php index 88cb4a753ad3..82369ff4ed00 100644 --- a/src/Illuminate/Cache/RedisTagSet.php +++ b/src/Illuminate/Cache/RedisTagSet.php @@ -13,7 +13,7 @@ class RedisTagSet extends TagSet * * @param string $key * @param int|null $ttl - * @param string $updateWhen + * @param string|null $updateWhen * @return void */ public function addEntry(string $key, ?int $ttl = null, $updateWhen = null) diff --git a/src/Illuminate/Cache/SessionStore.php b/src/Illuminate/Cache/SessionStore.php new file mode 100644 index 000000000000..0ed5a4da0609 --- /dev/null +++ b/src/Illuminate/Cache/SessionStore.php @@ -0,0 +1,206 @@ +key = $key; + $this->session = $session; + } + + /** + * Get all of the cached values and their expiration times. + * + * @return array + */ + public function all() + { + return $this->session->get($this->key, []); + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + if (! $this->session->exists($this->itemKey($key))) { + return; + } + + $item = $this->session->get($this->itemKey($key)); + + $expiresAt = $item['expiresAt'] ?? 0; + + if ($this->isExpired($expiresAt)) { + $this->forget($key); + + return; + } + + return $item['value']; + } + + /** + * Determine if the given expiration time is expired. + * + * @param int|float $expiresAt + * @return bool + */ + protected function isExpired($expiresAt) + { + return $expiresAt !== 0 && (Carbon::now()->getPreciseTimestamp(3) / 1000) >= $expiresAt; + } + + /** + * Store an item in the cache for a given number of seconds. + * + * @param string $key + * @param mixed $value + * @param int $seconds + * @return bool + */ + public function put($key, $value, $seconds) + { + $this->session->put($this->itemKey($key), [ + 'value' => $value, + 'expiresAt' => $this->toTimestamp($seconds), + ]); + + return true; + } + + /** + * Get the UNIX timestamp, with milliseconds, for the given number of seconds in the future. + * + * @param int $seconds + * @return float + */ + protected function toTimestamp($seconds) + { + return $seconds > 0 ? (Carbon::now()->getPreciseTimestamp(3) / 1000) + $seconds : 0; + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int + */ + public function increment($key, $value = 1) + { + if (! is_null($existing = $this->get($key))) { + return tap(((int) $existing) + $value, function ($incremented) use ($key) { + $this->session->put($this->itemKey("{$key}.value"), $incremented); + }); + } + + $this->forever($key, $value); + + return $value; + } + + /** + * Decrement the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int + */ + public function decrement($key, $value = 1) + { + return $this->increment($key, $value * -1); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return bool + */ + public function forever($key, $value) + { + return $this->put($key, $value, 0); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return bool + */ + public function forget($key) + { + if ($this->session->exists($this->itemKey($key))) { + $this->session->forget($this->itemKey($key)); + + return true; + } + + return false; + } + + /** + * Remove all items from the cache. + * + * @return bool + */ + public function flush() + { + $this->session->put($this->key, []); + + return true; + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function itemKey($key) + { + return "{$this->key}.{$key}"; + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return ''; + } +} diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index 51bd12c743ec..21f2fd6c4283 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -64,7 +64,7 @@ public static function add($array, $key, $value) /** * Get an array item from an array using "dot" notation. */ - public static function array(ArrayAccess|array $array, string|int|null $key, ?array $default = null): array + public static function array(ArrayAccess|array $array, string|int|null $key, ?array $default = []): array { $value = Arr::get($array, $key, $default); @@ -446,7 +446,7 @@ public static function get($array, $key, $default = null) } if (! str_contains($key, '.')) { - return $array[$key] ?? value($default); + return value($default); } foreach (explode('.', $key) as $segment) { diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index b01db682f42b..02303837aeb6 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1386,7 +1386,7 @@ public function splitIn($numberOfGroups) /** * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. * - * @param (callable(TValue, TKey): bool)|string $key + * @param (callable(TValue, TKey): bool)|string|null $key * @param mixed $operator * @param mixed $value * @return TValue diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index 0b434804d3a5..b59af1892dc6 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -985,7 +985,7 @@ public function sole($key = null, $operator = null, $value = null); /** * Get the first item in the collection but throw an exception if no matching items exist. * - * @param (callable(TValue, TKey): bool)|string $key + * @param (callable(TValue, TKey): bool)|string|null $key * @param mixed $operator * @param mixed $value * @return TValue diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index 6e1e5c2bc4ab..95b61720afc4 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -1344,7 +1344,7 @@ public function split($numberOfGroups) /** * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. * - * @param (callable(TValue, TKey): bool)|string $key + * @param (callable(TValue, TKey): bool)|string|null $key * @param mixed $operator * @param mixed $value * @return TValue @@ -1369,7 +1369,7 @@ public function sole($key = null, $operator = null, $value = null) /** * Get the first item in the collection but throw an exception if no matching items exist. * - * @param (callable(TValue, TKey): bool)|string $key + * @param (callable(TValue, TKey): bool)|string|null $key * @param mixed $operator * @param mixed $value * @return TValue @@ -1630,17 +1630,22 @@ public function takeUntil($value) } /** - * Take items in the collection until a given point in time. + * Take items in the collection until a given point in time, with an optional callback on timeout. * * @param \DateTimeInterface $timeout + * @param callable(TValue|null, TKey|null): mixed|null $callback * @return static */ - public function takeUntilTimeout(DateTimeInterface $timeout) + public function takeUntilTimeout(DateTimeInterface $timeout, ?callable $callback = null) { $timeout = $timeout->getTimestamp(); - return new static(function () use ($timeout) { + return new static(function () use ($timeout, $callback) { if ($this->now() >= $timeout) { + if ($callback) { + $callback(null, null); + } + return; } @@ -1648,6 +1653,10 @@ public function takeUntilTimeout(DateTimeInterface $timeout) yield $key => $value; if ($this->now() >= $timeout) { + if ($callback) { + $callback($value, $key); + } + break; } } diff --git a/src/Illuminate/Collections/Traits/TransformsToResourceCollection.php b/src/Illuminate/Collections/Traits/TransformsToResourceCollection.php index 22143b356c48..86aaebd4b11d 100644 --- a/src/Illuminate/Collections/Traits/TransformsToResourceCollection.php +++ b/src/Illuminate/Collections/Traits/TransformsToResourceCollection.php @@ -2,9 +2,12 @@ namespace Illuminate\Support\Traits; +use Illuminate\Database\Eloquent\Attributes\UseResource; +use Illuminate\Database\Eloquent\Attributes\UseResourceCollection; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Resources\Json\ResourceCollection; use LogicException; +use ReflectionClass; trait TransformsToResourceCollection { @@ -47,6 +50,18 @@ protected function guessResourceCollection(): ResourceCollection throw_unless(method_exists($className, 'guessResourceName'), LogicException::class, sprintf('Expected class %s to implement guessResourceName method. Make sure the model uses the TransformsToResource trait.', $className)); + $useResourceCollection = $this->resolveResourceCollectionFromAttribute($className); + + if ($useResourceCollection !== null && class_exists($useResourceCollection)) { + return new $useResourceCollection($this); + } + + $useResource = $this->resolveResourceFromAttribute($className); + + if ($useResource !== null && class_exists($useResource)) { + return $useResource::collection($this); + } + $resourceClasses = $className::guessResourceName(); foreach ($resourceClasses as $resourceClass) { @@ -65,4 +80,42 @@ protected function guessResourceCollection(): ResourceCollection throw new LogicException(sprintf('Failed to find resource class for model [%s].', $className)); } + + /** + * Get the resource class from the class attribute. + * + * @param class-string<\Illuminate\Http\Resources\Json\JsonResource> $class + * @return class-string<*>|null + */ + protected function resolveResourceFromAttribute(string $class): ?string + { + if (! class_exists($class)) { + return null; + } + + $attributes = (new ReflectionClass($class))->getAttributes(UseResource::class); + + return $attributes !== [] + ? $attributes[0]->newInstance()->class + : null; + } + + /** + * Get the resource collection class from the class attribute. + * + * @param class-string<\Illuminate\Http\Resources\Json\ResourceCollection> $class + * @return class-string<*>|null + */ + protected function resolveResourceCollectionFromAttribute(string $class): ?string + { + if (! class_exists($class)) { + return null; + } + + $attributes = (new ReflectionClass($class))->getAttributes(UseResourceCollection::class); + + return $attributes !== [] + ? $attributes[0]->newInstance()->class + : null; + } } diff --git a/src/Illuminate/Console/QuestionHelper.php b/src/Illuminate/Console/QuestionHelper.php index 5cbab6f0f1bc..64f8f26dbcd4 100644 --- a/src/Illuminate/Console/QuestionHelper.php +++ b/src/Illuminate/Console/QuestionHelper.php @@ -62,7 +62,7 @@ protected function writePrompt(OutputInterface $output, Question $question): voi if ($question instanceof ChoiceQuestion) { foreach ($question->getChoices() as $key => $value) { - with(new TwoColumnDetail($output))->render($value, $key); + (new TwoColumnDetail($output))->render($value, $key); } } diff --git a/src/Illuminate/Console/Scheduling/ScheduleWorkCommand.php b/src/Illuminate/Console/Scheduling/ScheduleWorkCommand.php index 647c4201b2d9..f91d3dc8b6ba 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleWorkCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleWorkCommand.php @@ -18,7 +18,9 @@ class ScheduleWorkCommand extends Command * * @var string */ - protected $signature = 'schedule:work {--run-output-file= : The file to direct schedule:run output to}'; + protected $signature = 'schedule:work + {--run-output-file= : The file to direct schedule:run output to} + {--whisper : Do not output message indicating that no jobs were ready to run}'; /** * The console command description. @@ -43,6 +45,10 @@ public function handle() $command = Application::formatCommandString('schedule:run'); + if ($this->option('whisper')) { + $command .= ' --whisper'; + } + if ($this->option('run-output-file')) { $command .= ' >> '.ProcessUtils::escapeArgument($this->option('run-output-file')).' 2>&1'; } diff --git a/src/Illuminate/Console/View/Components/Ask.php b/src/Illuminate/Console/View/Components/Ask.php index dfd414ad885d..b731408958b0 100644 --- a/src/Illuminate/Console/View/Components/Ask.php +++ b/src/Illuminate/Console/View/Components/Ask.php @@ -10,7 +10,7 @@ class Ask extends Component * Renders the component using the given arguments. * * @param string $question - * @param string $default + * @param string|null $default * @param bool $multiline * @return mixed */ diff --git a/src/Illuminate/Console/View/Components/AskWithCompletion.php b/src/Illuminate/Console/View/Components/AskWithCompletion.php index 103d73071b7a..c28f91236eba 100644 --- a/src/Illuminate/Console/View/Components/AskWithCompletion.php +++ b/src/Illuminate/Console/View/Components/AskWithCompletion.php @@ -11,7 +11,7 @@ class AskWithCompletion extends Component * * @param string $question * @param array|callable $choices - * @param string $default + * @param string|null $default * @return mixed */ public function render($question, $choices, $default = null) diff --git a/src/Illuminate/Console/View/Components/Choice.php b/src/Illuminate/Console/View/Components/Choice.php index ed215527ae8e..1f7818f742fe 100644 --- a/src/Illuminate/Console/View/Components/Choice.php +++ b/src/Illuminate/Console/View/Components/Choice.php @@ -12,7 +12,7 @@ class Choice extends Component * @param string $question * @param array $choices * @param mixed $default - * @param int $attempts + * @param int|null $attempts * @param bool $multiple * @return mixed */ diff --git a/src/Illuminate/Console/View/Components/Component.php b/src/Illuminate/Console/View/Components/Component.php index f515f916ff62..af1ce0ea9aaa 100644 --- a/src/Illuminate/Console/View/Components/Component.php +++ b/src/Illuminate/Console/View/Components/Component.php @@ -103,7 +103,7 @@ protected function mutate($data, $mutators) */ protected function usingQuestionHelper($callable) { - $property = with(new ReflectionClass(OutputStyle::class)) + $property = (new ReflectionClass(OutputStyle::class)) ->getParentClass() ->getProperty('questionHelper'); diff --git a/src/Illuminate/Console/View/Components/Error.php b/src/Illuminate/Console/View/Components/Error.php index 73196cc8440e..bb19ed42f106 100644 --- a/src/Illuminate/Console/View/Components/Error.php +++ b/src/Illuminate/Console/View/Components/Error.php @@ -15,6 +15,6 @@ class Error extends Component */ public function render($string, $verbosity = OutputInterface::VERBOSITY_NORMAL) { - with(new Line($this->output))->render('error', $string, $verbosity); + (new Line($this->output))->render('error', $string, $verbosity); } } diff --git a/src/Illuminate/Console/View/Components/Factory.php b/src/Illuminate/Console/View/Components/Factory.php index 2929279057ee..2dde02f72384 100644 --- a/src/Illuminate/Console/View/Components/Factory.php +++ b/src/Illuminate/Console/View/Components/Factory.php @@ -56,6 +56,6 @@ public function __call($method, $parameters) 'Console component [%s] not found.', $method ))); - return with(new $component($this->output))->render(...$parameters); + return (new $component($this->output))->render(...$parameters); } } diff --git a/src/Illuminate/Console/View/Components/Info.php b/src/Illuminate/Console/View/Components/Info.php index 765142246fed..ac6652907844 100644 --- a/src/Illuminate/Console/View/Components/Info.php +++ b/src/Illuminate/Console/View/Components/Info.php @@ -15,6 +15,6 @@ class Info extends Component */ public function render($string, $verbosity = OutputInterface::VERBOSITY_NORMAL) { - with(new Line($this->output))->render('info', $string, $verbosity); + (new Line($this->output))->render('info', $string, $verbosity); } } diff --git a/src/Illuminate/Console/View/Components/Success.php b/src/Illuminate/Console/View/Components/Success.php index 927cafe51e94..9b1419102516 100644 --- a/src/Illuminate/Console/View/Components/Success.php +++ b/src/Illuminate/Console/View/Components/Success.php @@ -15,6 +15,6 @@ class Success extends Component */ public function render($string, $verbosity = OutputInterface::VERBOSITY_NORMAL) { - with(new Line($this->output))->render('success', $string, $verbosity); + (new Line($this->output))->render('success', $string, $verbosity); } } diff --git a/src/Illuminate/Console/View/Components/Warn.php b/src/Illuminate/Console/View/Components/Warn.php index 20adb1f272b7..d00656ab010e 100644 --- a/src/Illuminate/Console/View/Components/Warn.php +++ b/src/Illuminate/Console/View/Components/Warn.php @@ -15,7 +15,6 @@ class Warn extends Component */ public function render($string, $verbosity = OutputInterface::VERBOSITY_NORMAL) { - with(new Line($this->output)) - ->render('warn', $string, $verbosity); + (new Line($this->output))->render('warn', $string, $verbosity); } } diff --git a/src/Illuminate/Container/Attributes/Give.php b/src/Illuminate/Container/Attributes/Give.php index 41523a84cc8c..f669c58d1abb 100644 --- a/src/Illuminate/Container/Attributes/Give.php +++ b/src/Illuminate/Container/Attributes/Give.php @@ -19,7 +19,7 @@ class Give implements ContextualAttribute */ public function __construct( public string $class, - public array $params = [] + public array $params = [], ) { } diff --git a/src/Illuminate/Container/BoundMethod.php b/src/Illuminate/Container/BoundMethod.php index 32c1da23ef32..9293a818d619 100644 --- a/src/Illuminate/Container/BoundMethod.php +++ b/src/Illuminate/Container/BoundMethod.php @@ -166,7 +166,7 @@ protected static function addDependencyForCallParameter( $container, $parameter, array &$parameters, - &$dependencies + &$dependencies, ) { $pendingDependencies = []; diff --git a/src/Illuminate/Contracts/Database/Eloquent/SupportsPartialRelations.php b/src/Illuminate/Contracts/Database/Eloquent/SupportsPartialRelations.php index c82125aa0c10..19cb15a825eb 100644 --- a/src/Illuminate/Contracts/Database/Eloquent/SupportsPartialRelations.php +++ b/src/Illuminate/Contracts/Database/Eloquent/SupportsPartialRelations.php @@ -9,7 +9,7 @@ interface SupportsPartialRelations * * @param string|null $column * @param string|\Closure|null $aggregate - * @param string $relation + * @param string|null $relation * @return $this */ public function ofMany($column = 'id', $aggregate = 'MAX', $relation = null); diff --git a/src/Illuminate/Database/ConcurrencyErrorDetector.php b/src/Illuminate/Database/ConcurrencyErrorDetector.php index 0b388111bb65..3a5cb1dcd0a6 100644 --- a/src/Illuminate/Database/ConcurrencyErrorDetector.php +++ b/src/Illuminate/Database/ConcurrencyErrorDetector.php @@ -33,6 +33,7 @@ public function causedByConcurrencyError(Throwable $e): bool 'has been chosen as the deadlock victim', 'Lock wait timeout exceeded; try restarting transaction', 'WSREP detected deadlock/conflict and aborted the transaction. Try restarting the transaction', + 'Record has changed since last read in table', ]); } } diff --git a/src/Illuminate/Database/Connection.php b/src/Illuminate/Database/Connection.php index 5ea30fdec2af..d6e6f3c7c30f 100755 --- a/src/Illuminate/Database/Connection.php +++ b/src/Illuminate/Database/Connection.php @@ -876,7 +876,7 @@ protected function getElapsedTime($start) * Register a callback to be invoked when the connection queries for longer than a given amount of time. * * @param \DateTimeInterface|\Carbon\CarbonInterval|float|int $threshold - * @param (callable(\Illuminate\Database\Connection, class-string<\Illuminate\Database\Events\QueryExecuted>): mixed) $handler + * @param (callable(\Illuminate\Database\Connection, \Illuminate\Database\Events\QueryExecuted): mixed) $handler * @return void */ public function whenQueryingForLongerThan($threshold, $handler) diff --git a/src/Illuminate/Database/Connectors/SQLiteConnector.php b/src/Illuminate/Database/Connectors/SQLiteConnector.php index 2e2ed8758919..858549ec55de 100755 --- a/src/Illuminate/Database/Connectors/SQLiteConnector.php +++ b/src/Illuminate/Database/Connectors/SQLiteConnector.php @@ -20,6 +20,7 @@ public function connect(array $config) $connection = $this->createConnection("sqlite:{$path}", $config, $options); + $this->configurePragmas($connection, $config); $this->configureForeignKeyConstraints($connection, $config); $this->configureBusyTimeout($connection, $config); $this->configureJournalMode($connection, $config); @@ -62,6 +63,24 @@ protected function parseDatabasePath(string $path): string return $path; } + /** + * Set miscellaneous user-configured pragmas. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configurePragmas($connection, array $config): void + { + if (! isset($config['pragmas'])) { + return; + } + + foreach ($config['pragmas'] as $pragma => $value) { + $connection->prepare("pragma {$pragma} = {$value}")->execute(); + } + } + /** * Enable or disable foreign key constraints if configured. * diff --git a/src/Illuminate/Database/DatabaseServiceProvider.php b/src/Illuminate/Database/DatabaseServiceProvider.php index 794090b34776..82ae4568fc51 100755 --- a/src/Illuminate/Database/DatabaseServiceProvider.php +++ b/src/Illuminate/Database/DatabaseServiceProvider.php @@ -96,6 +96,10 @@ protected function registerConnectionServices() */ protected function registerFakerGenerator() { + if (! class_exists(FakerGenerator::class)) { + return; + } + $this->app->singleton(FakerGenerator::class, function ($app, $parameters) { $locale = $parameters['locale'] ?? $app['config']->get('app.faker_locale', 'en_US'); diff --git a/src/Illuminate/Database/Eloquent/Attributes/UseResource.php b/src/Illuminate/Database/Eloquent/Attributes/UseResource.php new file mode 100644 index 000000000000..a1cbc48f3a90 --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Attributes/UseResource.php @@ -0,0 +1,18 @@ + $class + */ + public function __construct(public string $class) + { + } +} diff --git a/src/Illuminate/Database/Eloquent/Attributes/UseResourceCollection.php b/src/Illuminate/Database/Eloquent/Attributes/UseResourceCollection.php new file mode 100644 index 000000000000..c17e1f1768dd --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Attributes/UseResourceCollection.php @@ -0,0 +1,18 @@ + $class + */ + public function __construct(public string $class) + { + } +} diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 0d524e47f480..d32a17b207ef 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -242,6 +242,21 @@ public function withoutGlobalScopes(?array $scopes = null) return $this; } + /** + * Remove all global scopes except the given scopes. + * + * @param array $scopes + * @return $this + */ + public function withoutGlobalScopesExcept(array $scopes = []) + { + $this->withoutGlobalScopes( + array_diff(array_keys($this->scopes), $scopes) + ); + + return $this; + } + /** * Get an array of global scopes that were removed from the query. * diff --git a/src/Illuminate/Database/Eloquent/Casts/AsCollection.php b/src/Illuminate/Database/Eloquent/Casts/AsCollection.php index e36b13df2184..5f7d0845c8ff 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsCollection.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsCollection.php @@ -80,7 +80,7 @@ public static function of($map) * Specify the collection type for the cast. * * @param class-string $class - * @param array{class-string, string}|class-string $map + * @param array{class-string, string}|class-string|null $map * @return string */ public static function using($class, $map = null) diff --git a/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php b/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php index b5912fa20b10..ab122ef3af40 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php @@ -79,7 +79,7 @@ public static function of($map) * Specify the collection for the cast. * * @param class-string $class - * @param array{class-string, string}|class-string $map + * @param array{class-string, string}|class-string|null $map * @return string */ public static function using($class, $map = null) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index f86538ebdbed..7591252b5148 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -1788,6 +1788,10 @@ protected function isEnumCastable($key) return false; } + if (is_subclass_of($castType, Castable::class)) { + return false; + } + return enum_exists($castType); } diff --git a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php index 805cca3b21de..d51a862f17d2 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php @@ -837,7 +837,7 @@ public function orWhereAttachedTo($related, $relationshipName = null) * * @param mixed $relations * @param \Illuminate\Contracts\Database\Query\Expression|string $column - * @param string $function + * @param string|null $function * @return $this */ public function withAggregate($relations, $column, $function = null) diff --git a/src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php b/src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php index dfd11a86d70d..35e3f98cf9af 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php +++ b/src/Illuminate/Database/Eloquent/Concerns/TransformsToResource.php @@ -2,9 +2,11 @@ namespace Illuminate\Database\Eloquent\Concerns; +use Illuminate\Database\Eloquent\Attributes\UseResource; use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Support\Str; use LogicException; +use ReflectionClass; trait TransformsToResource { @@ -30,6 +32,12 @@ public function toResource(?string $resourceClass = null): JsonResource */ protected function guessResource(): JsonResource { + $resourceClass = $this->resolveResourceFromAttribute(static::class); + + if ($resourceClass !== null && class_exists($resourceClass)) { + return $resourceClass::make($this); + } + foreach (static::guessResourceName() as $resourceClass) { if (is_string($resourceClass) && class_exists($resourceClass)) { return $resourceClass::make($this); @@ -42,7 +50,7 @@ protected function guessResource(): JsonResource /** * Guess the resource class name for the model. * - * @return array> + * @return array{class-string<\Illuminate\Http\Resources\Json\JsonResource>, class-string<\Illuminate\Http\Resources\Json\JsonResource>} */ public static function guessResourceName(): array { @@ -67,4 +75,23 @@ class_basename($modelClass) return [$potentialResource.'Resource', $potentialResource]; } + + /** + * Get the resource class from the class attribute. + * + * @param class-string<\Illuminate\Http\Resources\Json\JsonResource> $class + * @return class-string<*>|null + */ + protected function resolveResourceFromAttribute(string $class): ?string + { + if (! class_exists($class)) { + return null; + } + + $attributes = (new ReflectionClass($class))->getAttributes(UseResource::class); + + return $attributes !== [] + ? $attributes[0]->newInstance()->class + : null; + } } diff --git a/src/Illuminate/Database/Eloquent/Factories/Factory.php b/src/Illuminate/Database/Eloquent/Factories/Factory.php index e3636c373e69..ed80b9fe5305 100644 --- a/src/Illuminate/Database/Eloquent/Factories/Factory.php +++ b/src/Illuminate/Database/Eloquent/Factories/Factory.php @@ -953,10 +953,14 @@ public static function dontExpandRelationshipsByDefault() /** * Get a new Faker instance. * - * @return \Faker\Generator + * @return \Faker\Generator|null */ protected function withFaker() { + if (! class_exists(Generator::class)) { + return; + } + return Container::getInstance()->make(Generator::class); } diff --git a/src/Illuminate/Database/Eloquent/Factories/Sequence.php b/src/Illuminate/Database/Eloquent/Factories/Sequence.php index 11971eced7da..4d350d2ad193 100644 --- a/src/Illuminate/Database/Eloquent/Factories/Sequence.php +++ b/src/Illuminate/Database/Eloquent/Factories/Sequence.php @@ -51,11 +51,13 @@ public function count(): int /** * Get the next value in the sequence. * + * @param array $attributes + * @param \Illuminate\Database\Eloquent\Model|null $parent * @return mixed */ - public function __invoke() + public function __invoke($attributes = [], $parent = null) { - return tap(value($this->sequence[$this->index % $this->count], $this), function () { + return tap(value($this->sequence[$this->index % $this->count], $this, $attributes, $parent), function () { $this->index = $this->index + 1; }); } diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php index 0c3029f1ab18..4be008110d82 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php @@ -468,7 +468,7 @@ public function get($columns = ['*']) * @param int|null $perPage * @param array $columns * @param string $pageName - * @param int $page + * @param int|null $page * @return \Illuminate\Pagination\LengthAwarePaginator */ public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) diff --git a/src/Illuminate/Database/Events/ModelPruningStarting.php b/src/Illuminate/Database/Events/ModelPruningStarting.php index a45f912dc283..581d3da8a4bc 100644 --- a/src/Illuminate/Database/Events/ModelPruningStarting.php +++ b/src/Illuminate/Database/Events/ModelPruningStarting.php @@ -10,7 +10,7 @@ class ModelPruningStarting * @param array $models The class names of the models that will be pruned. */ public function __construct( - public $models + public $models, ) { } } diff --git a/src/Illuminate/Database/Migrations/MigrationCreator.php b/src/Illuminate/Database/Migrations/MigrationCreator.php index ba98eb658148..8f6b5bd45a48 100755 --- a/src/Illuminate/Database/Migrations/MigrationCreator.php +++ b/src/Illuminate/Database/Migrations/MigrationCreator.php @@ -82,7 +82,7 @@ public function create($name, $path, $table = null, $create = false) * Ensure that a migration with the given name doesn't already exist. * * @param string $name - * @param string $migrationPath + * @param string|null $migrationPath * @return void * * @throws \InvalidArgumentException diff --git a/src/Illuminate/Database/Query/Expression.php b/src/Illuminate/Database/Query/Expression.php index 1568e1ff9436..839dea833a2c 100755 --- a/src/Illuminate/Database/Query/Expression.php +++ b/src/Illuminate/Database/Query/Expression.php @@ -16,7 +16,7 @@ class Expression implements ExpressionContract * @param TValue $value */ public function __construct( - protected $value + protected $value, ) { } diff --git a/src/Illuminate/Database/Schema/ForeignIdColumnDefinition.php b/src/Illuminate/Database/Schema/ForeignIdColumnDefinition.php index c7f66d19bb96..2501bebe68ab 100644 --- a/src/Illuminate/Database/Schema/ForeignIdColumnDefinition.php +++ b/src/Illuminate/Database/Schema/ForeignIdColumnDefinition.php @@ -46,7 +46,7 @@ public function constrained($table = null, $column = null, $indexName = null) * Specify which column this foreign ID references on another table. * * @param string $column - * @param string $indexName + * @param string|null $indexName * @return \Illuminate\Database\Schema\ForeignKeyDefinition */ public function references($column, $indexName = null) diff --git a/src/Illuminate/Database/Schema/SqliteSchemaState.php b/src/Illuminate/Database/Schema/SqliteSchemaState.php index bda420fefe31..23cf3896f41b 100644 --- a/src/Illuminate/Database/Schema/SqliteSchemaState.php +++ b/src/Illuminate/Database/Schema/SqliteSchemaState.php @@ -16,7 +16,7 @@ class SqliteSchemaState extends SchemaState */ public function dump(Connection $connection, $path) { - with($process = $this->makeProcess( + ($process = $this->makeProcess( $this->baseCommand().' ".schema --indent"' ))->setTimeout(null)->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ // @@ -39,7 +39,7 @@ public function dump(Connection $connection, $path) */ protected function appendMigrationData(string $path) { - with($process = $this->makeProcess( + ($process = $this->makeProcess( $this->baseCommand().' ".dump \''.$this->getMigrationTable().'\'"' ))->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ // diff --git a/src/Illuminate/Database/Seeder.php b/src/Illuminate/Database/Seeder.php index 08e57b2d7834..67b617ec8936 100755 --- a/src/Illuminate/Database/Seeder.php +++ b/src/Illuminate/Database/Seeder.php @@ -50,7 +50,7 @@ public function call($class, $silent = false, array $parameters = []) $name = get_class($seeder); if ($silent === false && isset($this->command)) { - with(new TwoColumnDetail($this->command->getOutput()))->render( + (new TwoColumnDetail($this->command->getOutput()))->render( $name, 'RUNNING' ); @@ -63,7 +63,7 @@ public function call($class, $silent = false, array $parameters = []) if ($silent === false && isset($this->command)) { $runTime = number_format((microtime(true) - $startTime) * 1000); - with(new TwoColumnDetail($this->command->getOutput()))->render( + (new TwoColumnDetail($this->command->getOutput()))->render( $name, "$runTime ms DONE" ); diff --git a/src/Illuminate/Foundation/Configuration/Middleware.php b/src/Illuminate/Foundation/Configuration/Middleware.php index 20b50499aedf..a458bf88ef47 100644 --- a/src/Illuminate/Foundation/Configuration/Middleware.php +++ b/src/Illuminate/Foundation/Configuration/Middleware.php @@ -555,8 +555,8 @@ public function redirectUsersTo(callable|string $redirect) /** * Configure where users are redirected by the authentication and guest middleware. * - * @param callable|string $guests - * @param callable|string $users + * @param callable|string|null $guests + * @param callable|string|null $users * @return $this */ public function redirectTo(callable|string|null $guests = null, callable|string|null $users = null) diff --git a/src/Illuminate/Foundation/Exceptions/Handler.php b/src/Illuminate/Foundation/Exceptions/Handler.php index 52342f698e80..cdd7b7d16736 100644 --- a/src/Illuminate/Foundation/Exceptions/Handler.php +++ b/src/Illuminate/Foundation/Exceptions/Handler.php @@ -1046,12 +1046,12 @@ public function renderForConsole($output, Throwable $e) if (! empty($alternatives = $e->getAlternatives())) { $message .= '. Did you mean one of these?'; - with(new Error($output))->render($message); - with(new BulletList($output))->render($alternatives); + (new Error($output))->render($message); + (new BulletList($output))->render($alternatives); $output->writeln(''); } else { - with(new Error($output))->render($message); + (new Error($output))->render($message); } return; diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php index c23ce0de4e37..db89e067c160 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php @@ -285,7 +285,7 @@ public function castAsJson($value, $connection = null) * Get the database connection. * * @param string|null $connection - * @param \Illuminate\Database\Eloquent\Model|class-string<\Illuminate\Database\Eloquent\Model>|string $table + * @param \Illuminate\Database\Eloquent\Model|class-string<\Illuminate\Database\Eloquent\Model>|string|null $table * @return \Illuminate\Database\Connection */ protected function getConnection($connection = null, $table = null) diff --git a/src/Illuminate/Http/Concerns/InteractsWithInput.php b/src/Illuminate/Http/Concerns/InteractsWithInput.php index a4e9173746a7..e4e1f647226c 100644 --- a/src/Illuminate/Http/Concerns/InteractsWithInput.php +++ b/src/Illuminate/Http/Concerns/InteractsWithInput.php @@ -250,7 +250,7 @@ public function file($key = null, $default = null) /** * Retrieve data from the instance. * - * @param string $key + * @param string|null $key * @param mixed $default * @return mixed */ diff --git a/src/Illuminate/Http/Exceptions/HttpResponseException.php b/src/Illuminate/Http/Exceptions/HttpResponseException.php index eeafe3205d60..ffa6d795b4f5 100644 --- a/src/Illuminate/Http/Exceptions/HttpResponseException.php +++ b/src/Illuminate/Http/Exceptions/HttpResponseException.php @@ -19,7 +19,7 @@ class HttpResponseException extends RuntimeException * Create a new HTTP response exception instance. * * @param \Symfony\Component\HttpFoundation\Response $response - * @param \Throwable $previous + * @param \Throwable|null $previous */ public function __construct(Response $response, ?Throwable $previous = null) { diff --git a/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php b/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php index 247c1507c506..b16b3c1bff25 100644 --- a/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php +++ b/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php @@ -24,7 +24,7 @@ public static function using($limit) * * @param \Illuminate\Http\Request $request * @param \Closure $next - * @param int $limit + * @param int|null $limit * @return \Illuminate\Http\Response */ public function handle($request, $next, $limit = null) diff --git a/src/Illuminate/Log/Events/MessageLogged.php b/src/Illuminate/Log/Events/MessageLogged.php index b3458815af5d..f7232700c5e7 100644 --- a/src/Illuminate/Log/Events/MessageLogged.php +++ b/src/Illuminate/Log/Events/MessageLogged.php @@ -4,38 +4,17 @@ class MessageLogged { - /** - * The log "level". - * - * @var string - */ - public $level; - - /** - * The log message. - * - * @var string - */ - public $message; - - /** - * The log context. - * - * @var array - */ - public $context; - /** * Create a new event instance. * - * @param string $level - * @param string $message - * @param array $context + * @param "emergency"|"alert"|"critical"|"error"|"warning"|"notice"|"info"|"debug" $level The log "level". + * @param string $message The log message. + * @param array $context The log context. */ - public function __construct($level, $message, array $context = []) - { - $this->level = $level; - $this->message = $message; - $this->context = $context; + public function __construct( + public $level, + public $message, + public array $context = [], + ) { } } diff --git a/src/Illuminate/Queue/Events/WorkerStarting.php b/src/Illuminate/Queue/Events/WorkerStarting.php index 89ada14873c0..dbe172457f40 100644 --- a/src/Illuminate/Queue/Events/WorkerStarting.php +++ b/src/Illuminate/Queue/Events/WorkerStarting.php @@ -14,7 +14,7 @@ class WorkerStarting public function __construct( public $connectionName, public $queue, - public $workerOptions + public $workerOptions, ) { } } diff --git a/src/Illuminate/Queue/Events/WorkerStopping.php b/src/Illuminate/Queue/Events/WorkerStopping.php index ae38a3d2c786..d4cf0ef1fbf0 100644 --- a/src/Illuminate/Queue/Events/WorkerStopping.php +++ b/src/Illuminate/Queue/Events/WorkerStopping.php @@ -12,7 +12,7 @@ class WorkerStopping */ public function __construct( public $status = 0, - public $workerOptions = null + public $workerOptions = null, ) { } } diff --git a/src/Illuminate/Queue/InteractsWithQueue.php b/src/Illuminate/Queue/InteractsWithQueue.php index 850c7c1c54bc..4a9728ae0824 100644 --- a/src/Illuminate/Queue/InteractsWithQueue.php +++ b/src/Illuminate/Queue/InteractsWithQueue.php @@ -211,7 +211,7 @@ public function assertNotFailed() /** * Assert that the job was released back onto the queue. * - * @param \DateTimeInterface|\DateInterval|int $delay + * @param \DateTimeInterface|\DateInterval|int|null $delay * @return $this */ public function assertReleased($delay = null) diff --git a/src/Illuminate/Redis/Connections/Connection.php b/src/Illuminate/Redis/Connections/Connection.php index 3e5338ee49a7..54fed48e7fe2 100644 --- a/src/Illuminate/Redis/Connections/Connection.php +++ b/src/Illuminate/Redis/Connections/Connection.php @@ -170,7 +170,7 @@ public function getName() } /** - * Set the connections name. + * Set the connection's name. * * @param string $name * @return $this diff --git a/src/Illuminate/Redis/Connectors/PhpRedisConnector.php b/src/Illuminate/Redis/Connectors/PhpRedisConnector.php index ec0dd08e333b..77be9335a5cf 100644 --- a/src/Illuminate/Redis/Connectors/PhpRedisConnector.php +++ b/src/Illuminate/Redis/Connectors/PhpRedisConnector.php @@ -143,6 +143,11 @@ protected function createClient(array $config) if (array_key_exists('compression_level', $config)) { $client->setOption(Redis::OPT_COMPRESSION_LEVEL, $config['compression_level']); } + + if (defined('Redis::OPT_PACK_IGNORE_NUMBERS') && + array_key_exists('pack_ignore_numbers', $config)) { + $client->setOption(Redis::OPT_PACK_IGNORE_NUMBERS, $config['pack_ignore_numbers']); + } }); } diff --git a/src/Illuminate/Routing/PendingResourceRegistration.php b/src/Illuminate/Routing/PendingResourceRegistration.php index 7e16f7e87ee9..a190538e9536 100644 --- a/src/Illuminate/Routing/PendingResourceRegistration.php +++ b/src/Illuminate/Routing/PendingResourceRegistration.php @@ -198,7 +198,7 @@ public function middlewareFor($methods, $middleware) * Specify middleware that should be removed from the resource routes. * * @param array|string $middleware - * @return $this|array + * @return $this */ public function withoutMiddleware($middleware) { diff --git a/src/Illuminate/Routing/PendingSingletonResourceRegistration.php b/src/Illuminate/Routing/PendingSingletonResourceRegistration.php index 2d845d300d2e..2c20e3c3168f 100644 --- a/src/Illuminate/Routing/PendingSingletonResourceRegistration.php +++ b/src/Illuminate/Routing/PendingSingletonResourceRegistration.php @@ -222,7 +222,7 @@ public function middlewareFor($methods, $middleware) * Specify middleware that should be removed from the resource routes. * * @param array|string $middleware - * @return $this|array + * @return $this */ public function withoutMiddleware($middleware) { diff --git a/src/Illuminate/Routing/RouteCollection.php b/src/Illuminate/Routing/RouteCollection.php index 9d6a087204c4..defdf55a17ed 100644 --- a/src/Illuminate/Routing/RouteCollection.php +++ b/src/Illuminate/Routing/RouteCollection.php @@ -79,7 +79,7 @@ protected function addLookups($route) // If the route has a name, we will add it to the name look-up table, so that we // will quickly be able to find the route associated with a name and not have // to iterate through every route every time we need to find a named route. - if ($name = $route->getName()) { + if (($name = $route->getName()) && ! $this->inNameLookup($name)) { $this->nameList[$name] = $route; } @@ -88,7 +88,7 @@ protected function addLookups($route) // processing a request and easily generate URLs to the given controllers. $action = $route->getAction(); - if (isset($action['controller'])) { + if (($controller = $action['controller'] ?? null) && ! $this->inActionLookup($controller)) { $this->addToActionList($action, $route); } } @@ -105,6 +105,28 @@ protected function addToActionList($action, $route) $this->actionList[trim($action['controller'], '\\')] = $route; } + /** + * Determine if the given controller is in the action lookup table. + * + * @param string $controller + * @return bool + */ + protected function inActionLookup($controller) + { + return array_key_exists($controller, $this->actionList); + } + + /** + * Determine if the given name is in the name lookup table. + * + * @param string $name + * @return bool + */ + protected function inNameLookup($name) + { + return array_key_exists($name, $this->nameList); + } + /** * Refresh the name look-up table. * @@ -117,8 +139,8 @@ public function refreshNameLookups() $this->nameList = []; foreach ($this->allRoutes as $route) { - if ($route->getName()) { - $this->nameList[$route->getName()] = $route; + if (($name = $route->getName()) && ! $this->inNameLookup($name)) { + $this->nameList[$name] = $route; } } } @@ -135,7 +157,7 @@ public function refreshActionLookups() $this->actionList = []; foreach ($this->allRoutes as $route) { - if (isset($route->getAction()['controller'])) { + if (($controller = $route->getAction()['controller'] ?? null) && ! $this->inActionLookup($controller)) { $this->addToActionList($route->getAction(), $route); } } diff --git a/src/Illuminate/Routing/RouteUrlGenerator.php b/src/Illuminate/Routing/RouteUrlGenerator.php index 7798bdc2841f..245c4c01156d 100644 --- a/src/Illuminate/Routing/RouteUrlGenerator.php +++ b/src/Illuminate/Routing/RouteUrlGenerator.php @@ -117,7 +117,7 @@ public function to($route, $parameters = [], $absolute = false) * * @param \Illuminate\Routing\Route $route * @param array $parameters - * @return string + * @return string|null */ protected function getRouteDomain($route, &$parameters) { diff --git a/src/Illuminate/Session/Store.php b/src/Illuminate/Session/Store.php index 6e76072fc6e6..e9f98383ec2c 100755 --- a/src/Illuminate/Session/Store.php +++ b/src/Illuminate/Session/Store.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Session\Session; use Illuminate\Support\Arr; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Date; use Illuminate\Support\MessageBag; use Illuminate\Support\Str; @@ -539,6 +540,16 @@ public function flashInput(array $value) $this->flash('_old_input', $value); } + /** + * Get the session cache instance. + * + * @return \Illuminate\Contracts\Cache\Repository + */ + public function cache() + { + return Cache::store('session'); + } + /** * Remove an item from the session, returning its value. * diff --git a/src/Illuminate/Support/AggregateServiceProvider.php b/src/Illuminate/Support/AggregateServiceProvider.php index d7425c5c2586..3d162c6ab95b 100644 --- a/src/Illuminate/Support/AggregateServiceProvider.php +++ b/src/Illuminate/Support/AggregateServiceProvider.php @@ -7,14 +7,14 @@ class AggregateServiceProvider extends ServiceProvider /** * The provider class names. * - * @var array + * @var array> */ protected $providers = []; /** * An array of the service provider instances. * - * @var array + * @var array */ protected $instances = []; @@ -35,7 +35,7 @@ public function register() /** * Get the services provided by the provider. * - * @return array + * @return array */ public function provides() { diff --git a/src/Illuminate/Support/Env.php b/src/Illuminate/Support/Env.php index 01747846ffa5..c84e944b25a9 100644 --- a/src/Illuminate/Support/Env.php +++ b/src/Illuminate/Support/Env.php @@ -285,7 +285,7 @@ protected static function getOption($key) */ protected static function prepareQuotedValue(string $input) { - return strpos($input, '"') !== false + return str_contains($input, '"') ? "'".self::addSlashesExceptFor($input, ['"'])."'" : '"'.self::addSlashesExceptFor($input, ["'"]).'"'; } diff --git a/src/Illuminate/Support/Facades/Http.php b/src/Illuminate/Support/Facades/Http.php index f4c423c74f60..6ec81c4a19c7 100644 --- a/src/Illuminate/Support/Facades/Http.php +++ b/src/Illuminate/Support/Facades/Http.php @@ -117,7 +117,7 @@ protected static function getFacadeAccessor() /** * Register a stub callable that will intercept requests and be able to return stub responses. * - * @param \Closure|array $callback + * @param \Closure|array|null $callback * @return \Illuminate\Http\Client\Factory */ public static function fake($callback = null) diff --git a/src/Illuminate/Support/Facades/Session.php b/src/Illuminate/Support/Facades/Session.php index 0a124624c249..8bc63adfaf78 100755 --- a/src/Illuminate/Support/Facades/Session.php +++ b/src/Illuminate/Support/Facades/Session.php @@ -41,6 +41,7 @@ * @method static void reflash() * @method static void keep(mixed $keys = null) * @method static void flashInput(array $value) + * @method static \Illuminate\Contracts\Cache\Repository cache() * @method static mixed remove(string $key) * @method static void forget(string|array $keys) * @method static void flush() diff --git a/src/Illuminate/Support/Fluent.php b/src/Illuminate/Support/Fluent.php index e504aa78d036..a15085727086 100755 --- a/src/Illuminate/Support/Fluent.php +++ b/src/Illuminate/Support/Fluent.php @@ -153,7 +153,7 @@ public function all($keys = null) /** * Get data from the fluent instance. * - * @param string $key + * @param string|null $key * @param mixed $default * @return mixed */ diff --git a/src/Illuminate/Support/InteractsWithTime.php b/src/Illuminate/Support/InteractsWithTime.php index 6b78be16c9b0..43a747cb2e4e 100644 --- a/src/Illuminate/Support/InteractsWithTime.php +++ b/src/Illuminate/Support/InteractsWithTime.php @@ -67,7 +67,7 @@ protected function currentTime() * Given a start time, format the total run time for human readability. * * @param float $startTime - * @param float $endTime + * @param float|null $endTime * @return string */ protected function runTimeForHumans($startTime, $endTime = null) diff --git a/src/Illuminate/Support/ServiceProvider.php b/src/Illuminate/Support/ServiceProvider.php index 85b0ee116791..ce2b526d2be1 100755 --- a/src/Illuminate/Support/ServiceProvider.php +++ b/src/Illuminate/Support/ServiceProvider.php @@ -547,7 +547,7 @@ public static function defaultProviders() * Add the given provider to the application's provider bootstrap file. * * @param string $provider - * @param string $path + * @param string|null $path * @return bool */ public static function addProviderToBootstrapFile(string $provider, ?string $path = null) diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index 16d524eb93f6..a8b116b80e62 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -1045,7 +1045,7 @@ public function take(int $limit) /** * Trim the string of the given characters. * - * @param string $characters + * @param string|null $characters * @return static */ public function trim($characters = null) @@ -1056,7 +1056,7 @@ public function trim($characters = null) /** * Left trim the string of the given characters. * - * @param string $characters + * @param string|null $characters * @return static */ public function ltrim($characters = null) @@ -1067,7 +1067,7 @@ public function ltrim($characters = null) /** * Right trim the string of the given characters. * - * @param string $characters + * @param string|null $characters * @return static */ public function rtrim($characters = null) diff --git a/src/Illuminate/Validation/Concerns/FilterEmailValidation.php b/src/Illuminate/Validation/Concerns/FilterEmailValidation.php index 50acbcf1311a..6cc4b8730982 100644 --- a/src/Illuminate/Validation/Concerns/FilterEmailValidation.php +++ b/src/Illuminate/Validation/Concerns/FilterEmailValidation.php @@ -18,7 +18,7 @@ class FilterEmailValidation implements EmailValidation /** * Create a new validation instance. * - * @param int $flags + * @param int|null $flags */ public function __construct($flags = null) { diff --git a/src/Illuminate/Validation/Concerns/ReplacesAttributes.php b/src/Illuminate/Validation/Concerns/ReplacesAttributes.php index be9abf169e8b..23cbfb9c0448 100644 --- a/src/Illuminate/Validation/Concerns/ReplacesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ReplacesAttributes.php @@ -598,7 +598,7 @@ protected function replaceRequiredIfAccepted($message, $attribute, $rule, $param * @param array $parameters * @return string */ - public function replaceRequiredIfDeclined($message, $attribute, $rule, $parameters) + protected function replaceRequiredIfDeclined($message, $attribute, $rule, $parameters) { return $this->replaceRequiredIfAccepted($message, $attribute, $rule, $parameters); } @@ -662,7 +662,7 @@ protected function replaceProhibitedIfAccepted($message, $attribute, $rule, $par * @param array $parameters * @return string */ - public function replaceProhibitedIfDeclined($message, $attribute, $rule, $parameters) + protected function replaceProhibitedIfDeclined($message, $attribute, $rule, $parameters) { return $this->replaceRequiredIfAccepted($message, $attribute, $rule, $parameters); } diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php index 0fcd7eb50b40..5d18ceba448f 100755 --- a/src/Illuminate/Validation/Validator.php +++ b/src/Illuminate/Validation/Validator.php @@ -309,7 +309,7 @@ class Validator implements ValidatorContract /** * The current random hash for the validator. * - * @var string + * @var string|null */ protected static $placeholderHash; diff --git a/src/Illuminate/View/DynamicComponent.php b/src/Illuminate/View/DynamicComponent.php index b34d3759d086..30ddd9114ab5 100644 --- a/src/Illuminate/View/DynamicComponent.php +++ b/src/Illuminate/View/DynamicComponent.php @@ -2,11 +2,14 @@ namespace Illuminate\View; +use BackedEnum; use Illuminate\Container\Container; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Illuminate\View\Compilers\ComponentTagCompiler; +use function Illuminate\Support\enum_value; + class DynamicComponent extends Component { /** @@ -33,11 +36,11 @@ class DynamicComponent extends Component /** * Create a new component instance. * - * @param string $component + * @param \BackedEnum|string $component */ - public function __construct(string $component) + public function __construct(BackedEnum|string $component) { - $this->component = $component; + $this->component = (string) enum_value($component); } /** diff --git a/tests/Cache/CacheSessionStoreTest.php b/tests/Cache/CacheSessionStoreTest.php new file mode 100755 index 000000000000..08ac6d4cdd7a --- /dev/null +++ b/tests/Cache/CacheSessionStoreTest.php @@ -0,0 +1,229 @@ +put('foo', 'bar', 10); + $this->assertTrue($result); + $this->assertSame('bar', $store->get('foo')); + } + + public function testCacheTtl(): void + { + $store = new SessionStore(self::getSession()); + + Carbon::setTestNow('2000-01-01 00:00:00.500'); // 500 milliseconds past + $store->put('hello', 'world', 1); + + Carbon::setTestNow('2000-01-01 00:00:01.499'); // progress 0.999 seconds + $this->assertSame('world', $store->get('hello')); + + Carbon::setTestNow('2000-01-01 00:00:01.500'); // progress 0.001 seconds. 1 second since putting into cache. + $this->assertNull($store->get('hello')); + } + + public function testMultipleItemsCanBeSetAndRetrieved() + { + $store = new SessionStore(self::getSession()); + $result = $store->put('foo', 'bar', 10); + $resultMany = $store->putMany([ + 'fizz' => 'buz', + 'quz' => 'baz', + ], 10); + $this->assertTrue($result); + $this->assertTrue($resultMany); + $this->assertEquals([ + 'foo' => 'bar', + 'fizz' => 'buz', + 'quz' => 'baz', + 'norf' => null, + ], $store->many(['foo', 'fizz', 'quz', 'norf'])); + } + + public function testItemsCanExpire() + { + Carbon::setTestNow(Carbon::now()); + + $store = new SessionStore(self::getSession()); + + $store->put('foo', 'bar', 10); + Carbon::setTestNow(Carbon::now()->addSeconds(10)->addSecond()); + $result = $store->get('foo'); + + $this->assertNull($result); + } + + public function testStoreItemForeverProperlyStoresInArray() + { + $mock = $this->getMockBuilder(SessionStore::class) + ->setConstructorArgs([self::getSession()]) + ->onlyMethods(['put']) + ->getMock(); + $mock->expects($this->once()) + ->method('put')->with($this->equalTo('foo'), $this->equalTo('bar'), $this->equalTo(0)) + ->willReturn(true); + $result = $mock->forever('foo', 'bar'); + $this->assertTrue($result); + } + + public function testValuesCanBeIncremented() + { + $store = new SessionStore(self::getSession()); + $store->put('foo', 1, 10); + $result = $store->increment('foo'); + $this->assertEquals(2, $result); + $this->assertEquals(2, $store->get('foo')); + + $result = $store->increment('foo', 2); + $this->assertEquals(4, $result); + $this->assertEquals(4, $store->get('foo')); + } + + public function testValuesGetCastedByIncrementOrDecrement() + { + $store = new SessionStore(self::getSession()); + $store->put('foo', '1', 10); + $result = $store->increment('foo'); + $this->assertEquals(2, $result); + $this->assertEquals(2, $store->get('foo')); + + $store->put('bar', '1', 10); + $result = $store->decrement('bar'); + $this->assertEquals(0, $result); + $this->assertEquals(0, $store->get('bar')); + } + + public function testIncrementNonNumericValues() + { + $store = new SessionStore(self::getSession()); + $store->put('foo', 'I am string', 10); + $result = $store->increment('foo'); + $this->assertEquals(1, $result); + $this->assertEquals(1, $store->get('foo')); + } + + public function testNonExistingKeysCanBeIncremented() + { + $store = new SessionStore(self::getSession()); + $result = $store->increment('foo'); + $this->assertEquals(1, $result); + $this->assertEquals(1, $store->get('foo')); + + // Will be there forever + Carbon::setTestNow(Carbon::now()->addYears(10)); + $this->assertEquals(1, $store->get('foo')); + } + + public function testExpiredKeysAreIncrementedLikeNonExistingKeys() + { + Carbon::setTestNow(Carbon::now()); + + $store = new SessionStore(self::getSession()); + + $store->put('foo', 999, 10); + Carbon::setTestNow(Carbon::now()->addSeconds(10)->addSecond()); + $result = $store->increment('foo'); + + $this->assertEquals(1, $result); + } + + public function testValuesCanBeDecremented() + { + $store = new SessionStore(self::getSession()); + $store->put('foo', 1, 10); + $result = $store->decrement('foo'); + $this->assertEquals(0, $result); + $this->assertEquals(0, $store->get('foo')); + + $result = $store->decrement('foo', 2); + $this->assertEquals(-2, $result); + $this->assertEquals(-2, $store->get('foo')); + } + + public function testItemsCanBeRemoved() + { + $store = new SessionStore(self::getSession()); + $store->put('foo', 'bar', 10); + $this->assertTrue($store->forget('foo')); + $this->assertNull($store->get('foo')); + $this->assertFalse($store->forget('foo')); + } + + public function testItemsCanBeFlushed() + { + $store = new SessionStore(self::getSession()); + $store->put('foo', 'bar', 10); + $store->put('baz', 'boom', 10); + $result = $store->flush(); + $this->assertTrue($result); + $this->assertNull($store->get('foo')); + $this->assertNull($store->get('baz')); + } + + public function testCacheKey() + { + $store = new SessionStore(self::getSession()); + $this->assertEmpty($store->getPrefix()); + } + + public function testItemKey() + { + $store = new SessionStore(self::getSession(), 'custom_prefix'); + $this->assertEquals('custom_prefix.foo', $store->itemKey('foo')); + } + + public function testValuesAreStoredByReference() + { + $store = new SessionStore(self::getSession()); + $object = new stdClass; + $object->foo = true; + + $store->put('object', $object, 10); + $object->bar = true; + + $retrievedObject = $store->get('object'); + + $this->assertTrue($retrievedObject->foo); + $this->assertTrue($retrievedObject->bar); + } + + public function testCanGetAll() + { + Carbon::setTestNow(Carbon::now()); + + $store = new SessionStore(self::getSession()); + $store->put('foo', 'bar', 10); + + $this->assertEquals([ + 'foo' => ['value' => 'bar', 'expiresAt' => Carbon::now()->addSeconds(10)->getPreciseTimestamp(3) / 1000], + ], $store->all()); + } + + protected static function getSession() + { + return new Store( + name: 'name', + serialization: 'php', + handler: new ArraySessionHandler(10), + id: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + ); + } +} diff --git a/tests/Console/View/ComponentsTest.php b/tests/Console/View/ComponentsTest.php index 31958ba6cb8f..82c789196717 100644 --- a/tests/Console/View/ComponentsTest.php +++ b/tests/Console/View/ComponentsTest.php @@ -21,7 +21,7 @@ public function testAlert() { $output = new BufferedOutput(); - with(new Components\Alert($output))->render('The application is in the [production] environment'); + (new Components\Alert($output))->render('The application is in the [production] environment'); $this->assertStringContainsString( 'THE APPLICATION IS IN THE [PRODUCTION] ENVIRONMENT.', @@ -33,7 +33,7 @@ public function testBulletList() { $output = new BufferedOutput(); - with(new Components\BulletList($output))->render([ + (new Components\BulletList($output))->render([ 'ls -la', 'php artisan inspire', ]); @@ -48,7 +48,7 @@ public function testSuccess() { $output = new BufferedOutput(); - with(new Components\Success($output))->render('The application is in the [production] environment'); + (new Components\Success($output))->render('The application is in the [production] environment'); $this->assertStringContainsString('SUCCESS The application is in the [production] environment.', $output->fetch()); } @@ -57,7 +57,7 @@ public function testError() { $output = new BufferedOutput(); - with(new Components\Error($output))->render('The application is in the [production] environment'); + (new Components\Error($output))->render('The application is in the [production] environment'); $this->assertStringContainsString('ERROR The application is in the [production] environment.', $output->fetch()); } @@ -66,7 +66,7 @@ public function testInfo() { $output = new BufferedOutput(); - with(new Components\Info($output))->render('The application is in the [production] environment'); + (new Components\Info($output))->render('The application is in the [production] environment'); $this->assertStringContainsString('INFO The application is in the [production] environment.', $output->fetch()); } @@ -80,7 +80,7 @@ public function testConfirm() ->once() ->andReturnTrue(); - $result = with(new Components\Confirm($output))->render('Question?'); + $result = (new Components\Confirm($output))->render('Question?'); $this->assertTrue($result); $output->shouldReceive('confirm') @@ -88,7 +88,7 @@ public function testConfirm() ->once() ->andReturnTrue(); - $result = with(new Components\Confirm($output))->render('Question?', true); + $result = (new Components\Confirm($output))->render('Question?', true); $this->assertTrue($result); } @@ -101,7 +101,7 @@ public function testChoice() ->once() ->andReturn('a'); - $result = with(new Components\Choice($output))->render('Question?', ['a', 'b']); + $result = (new Components\Choice($output))->render('Question?', ['a', 'b']); $this->assertSame('a', $result); } @@ -109,17 +109,17 @@ public function testTask() { $output = new BufferedOutput(); - with(new Components\Task($output))->render('My task', fn () => MigrationResult::Success->value); + (new Components\Task($output))->render('My task', fn () => MigrationResult::Success->value); $result = $output->fetch(); $this->assertStringContainsString('My task', $result); $this->assertStringContainsString('DONE', $result); - with(new Components\Task($output))->render('My task', fn () => MigrationResult::Failure->value); + (new Components\Task($output))->render('My task', fn () => MigrationResult::Failure->value); $result = $output->fetch(); $this->assertStringContainsString('My task', $result); $this->assertStringContainsString('FAIL', $result); - with(new Components\Task($output))->render('My task', fn () => MigrationResult::Skipped->value); + (new Components\Task($output))->render('My task', fn () => MigrationResult::Skipped->value); $result = $output->fetch(); $this->assertStringContainsString('My task', $result); $this->assertStringContainsString('SKIPPED', $result); @@ -129,7 +129,7 @@ public function testTwoColumnDetail() { $output = new BufferedOutput(); - with(new Components\TwoColumnDetail($output))->render('First', 'Second'); + (new Components\TwoColumnDetail($output))->render('First', 'Second'); $result = $output->fetch(); $this->assertStringContainsString('First', $result); $this->assertStringContainsString('Second', $result); @@ -139,7 +139,7 @@ public function testWarn() { $output = new BufferedOutput(); - with(new Components\Warn($output))->render('The application is in the [production] environment'); + (new Components\Warn($output))->render('The application is in the [production] environment'); $this->assertStringContainsString('WARN The application is in the [production] environment.', $output->fetch()); } diff --git a/tests/Database/DatabaseEloquentFactoryTest.php b/tests/Database/DatabaseEloquentFactoryTest.php index ac98ceb2fa41..5bfa2f2a6540 100644 --- a/tests/Database/DatabaseEloquentFactoryTest.php +++ b/tests/Database/DatabaseEloquentFactoryTest.php @@ -533,6 +533,39 @@ public function test_counted_sequence() $this->assertSame(3, $value); } + public function test_sequence_with_has_many_relationship() + { + $users = FactoryTestUserFactory::times(2) + ->sequence( + ['name' => 'Abigail Otwell'], + ['name' => 'Taylor Otwell'], + ) + ->has( + FactoryTestPostFactory::times(3) + ->state(['title' => 'Post']) + ->sequence(function ($sequence, $attributes, $user) { + return ['title' => $user->name.' '.$attributes['title'].' '.($sequence->index % 3 + 1)]; + }), + 'posts' + ) + ->create(); + + $this->assertCount(2, FactoryTestUser::all()); + $this->assertCount(6, FactoryTestPost::all()); + $this->assertCount(3, FactoryTestUser::latest()->first()->posts); + $this->assertEquals( + FactoryTestPost::orderBy('title')->pluck('title')->all(), + [ + 'Abigail Otwell Post 1', + 'Abigail Otwell Post 2', + 'Abigail Otwell Post 3', + 'Taylor Otwell Post 1', + 'Taylor Otwell Post 2', + 'Taylor Otwell Post 3', + ] + ); + } + public function test_cross_join_sequences() { $assert = function ($users) { diff --git a/tests/Database/DatabaseEloquentGlobalScopesTest.php b/tests/Database/DatabaseEloquentGlobalScopesTest.php index 3b5a379d4fdb..d86bf2deb0eb 100644 --- a/tests/Database/DatabaseEloquentGlobalScopesTest.php +++ b/tests/Database/DatabaseEloquentGlobalScopesTest.php @@ -108,6 +108,18 @@ public function testAllGlobalScopesCanBeRemoved() $this->assertEquals([], $query->getBindings()); } + public function testAllGlobalScopesCanBeRemovedExceptSpecified() + { + $model = new EloquentClosureGlobalScopesTestModel; + $query = $model->newQuery()->withoutGlobalScopesExcept(['active_scope']); + $this->assertSame('select * from "table" where "active" = ?', $query->toSql()); + $this->assertEquals([1], $query->getBindings()); + + $query = EloquentClosureGlobalScopesTestModel::withoutGlobalScopesExcept(['active_scope']); + $this->assertSame('select * from "table" where "active" = ?', $query->toSql()); + $this->assertEquals([1], $query->getBindings()); + } + public function testGlobalScopesWithOrWhereConditionsAreNested() { $model = new EloquentClosureGlobalScopesWithOrTestModel; diff --git a/tests/Database/DatabaseEloquentResourceCollectionTest.php b/tests/Database/DatabaseEloquentResourceCollectionTest.php index e2a1232c3694..b51c93ed05a4 100644 --- a/tests/Database/DatabaseEloquentResourceCollectionTest.php +++ b/tests/Database/DatabaseEloquentResourceCollectionTest.php @@ -3,8 +3,14 @@ namespace Illuminate\Tests\Database; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Http\Resources\Json\AnonymousResourceCollection; use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceCollectionTestModel; +use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithUseResourceAttribute; +use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithUseResourceCollectionAttribute; +use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceCollectionTestResource; +use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResource; +use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResourceCollection; use PHPUnit\Framework\TestCase; class DatabaseEloquentResourceCollectionTest extends TestCase @@ -43,9 +49,27 @@ class_alias(EloquentResourceCollectionTestResource::class, 'Illuminate\Tests\Dat $this->assertInstanceOf(JsonResource::class, $resource); } -} -class EloquentResourceCollectionTestResource extends JsonResource -{ - // + public function testItCanTransformToResourceViaUseResourceAttribute() + { + $collection = new Collection([ + new EloquentResourceTestResourceModelWithUseResourceCollectionAttribute(), + ]); + + $resource = $collection->toResourceCollection(); + + $this->assertInstanceOf(EloquentResourceTestJsonResourceCollection::class, $resource); + } + + public function testItCanTransformToResourceViaUseResourceCollectionAttribute() + { + $collection = new Collection([ + new EloquentResourceTestResourceModelWithUseResourceAttribute(), + ]); + + $resource = $collection->toResourceCollection(); + + $this->assertInstanceOf(AnonymousResourceCollection::class, $resource); + $this->assertInstanceOf(EloquentResourceTestJsonResource::class, $resource[0]); + } } diff --git a/tests/Database/DatabaseEloquentResourceModelTest.php b/tests/Database/DatabaseEloquentResourceModelTest.php index 0be4eb3ad83d..cc2a9f6c3813 100644 --- a/tests/Database/DatabaseEloquentResourceModelTest.php +++ b/tests/Database/DatabaseEloquentResourceModelTest.php @@ -2,9 +2,10 @@ namespace Illuminate\Tests\Database; -use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModel; use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithGuessableResource; +use Illuminate\Tests\Database\Fixtures\Models\EloquentResourceTestResourceModelWithUseResourceAttribute; +use Illuminate\Tests\Database\Fixtures\Resources\EloquentResourceTestJsonResource; use PHPUnit\Framework\TestCase; class DatabaseEloquentResourceModelTest extends TestCase @@ -59,9 +60,14 @@ public function testItCanGuessResourceName() 'Illuminate\Tests\Database\Fixtures\Http\Resources\EloquentResourceTestResourceModel', ], $model::guessResourceName()); } -} -class EloquentResourceTestJsonResource extends JsonResource -{ - // + public function testItCanTransformToResourceViaUseResourceAttribute() + { + $model = new EloquentResourceTestResourceModelWithUseResourceAttribute(); + + $resource = $model->toResource(); + + $this->assertInstanceOf(EloquentResourceTestJsonResource::class, $resource); + $this->assertSame($model, $resource->resource); + } } diff --git a/tests/Database/DatabaseTransactionsManagerTest.php b/tests/Database/DatabaseTransactionsManagerTest.php index 0cf4f8bdc34a..f1564aa8f3ec 100755 --- a/tests/Database/DatabaseTransactionsManagerTest.php +++ b/tests/Database/DatabaseTransactionsManagerTest.php @@ -109,6 +109,31 @@ public function testCallbacksAreAddedToTheCurrentTransaction() $this->assertCount(1, $manager->getPendingTransactions()[2]->getCallbacks()); } + public function testCallbacksRunInFifoOrder() + { + $manager = (new DatabaseTransactionsManager); + + $order = []; + + $manager->begin('default', 1); + + $manager->addCallback(function () use (&$order) { + $order[] = 1; + }); + + $manager->addCallback(function () use (&$order) { + $order[] = 2; + }); + + $manager->addCallback(function () use (&$order) { + $order[] = 3; + }); + + $manager->commit('default', 1, 0); + + $this->assertSame([1, 2, 3], $order); + } + public function testCommittingTransactionsExecutesCallbacks() { $callbacks = []; diff --git a/tests/Database/Fixtures/Models/EloquentResourceTestResourceModelWithUseResourceAttribute.php b/tests/Database/Fixtures/Models/EloquentResourceTestResourceModelWithUseResourceAttribute.php new file mode 100644 index 000000000000..12ff9d1ea528 --- /dev/null +++ b/tests/Database/Fixtures/Models/EloquentResourceTestResourceModelWithUseResourceAttribute.php @@ -0,0 +1,13 @@ + 12345, 'journal_mode' => 'wal', 'synchronous' => 'normal', + 'pragmas' => [ + 'query_only' => true, + ], ])->getSchemaBuilder(); $this->assertSame(1, $schema->pragma('foreign_keys')); $this->assertSame(12345, $schema->pragma('busy_timeout')); $this->assertSame('wal', $schema->pragma('journal_mode')); $this->assertSame(1, $schema->pragma('synchronous')); + $this->assertSame(1, $schema->pragma('query_only')); $schema->pragma('foreign_keys', 0); $schema->pragma('busy_timeout', 54321); diff --git a/tests/Integration/Routing/CompiledRouteCollectionTest.php b/tests/Integration/Routing/CompiledRouteCollectionTest.php index fcaa4dad5d4e..35d12753b5ca 100644 --- a/tests/Integration/Routing/CompiledRouteCollectionTest.php +++ b/tests/Integration/Routing/CompiledRouteCollectionTest.php @@ -94,6 +94,38 @@ public function testRouteCollectionCanRetrieveByAction() $this->assertSame($action, Arr::except($route->getAction(), 'as')); } + public function testCompiledAndNonCompiledUrlResolutionHasSamePrecedenceForActions() + { + @unlink(__DIR__.'/Fixtures/cache/routes-v7.php'); + $this->app->useBootstrapPath(__DIR__.'/Fixtures'); + $app = (static function () { + $refresh = true; + + return require __DIR__.'/Fixtures/app.php'; + })(); + $app['router']->get('/foo/{bar}', ['FooController', 'show']); + $app['router']->get('/foo/{bar}/{baz}', ['FooController', 'show']); + $app['router']->getRoutes()->refreshActionLookups(); + + $this->assertSame('foo/{bar}', $app['router']->getRoutes()->getByAction('FooController@show')->uri); + + $this->artisan('route:cache')->assertExitCode(0); + require __DIR__.'/Fixtures/cache/routes-v7.php'; + + $this->assertSame('foo/{bar}', $app['router']->getRoutes()->getByAction('FooController@show')->uri); + + unlink(__DIR__.'/Fixtures/cache/routes-v7.php'); + } + + public function testCompiledAndNonCompiledUrlResolutionHasSamePrecedenceForNames() + { + $this->router->get('/foo/{bar}', ['FooController', 'show'])->name('foo.show'); + $this->router->get('/foo/{bar}/{baz}', ['FooController', 'show'])->name('foo.show'); + $this->router->getRoutes()->refreshNameLookups(); + + $this->assertSame('foo/{bar}', $this->router->getRoutes()->getByName('foo.show')->uri); + } + public function testRouteCollectionCanGetIterator() { $this->routeCollection->add($this->newRoute('GET', 'foo/index', [ diff --git a/tests/Integration/Routing/Fixtures/app.php b/tests/Integration/Routing/Fixtures/app.php new file mode 100644 index 000000000000..f0921fd8b3ae --- /dev/null +++ b/tests/Integration/Routing/Fixtures/app.php @@ -0,0 +1,18 @@ +create(); +} else { + return AppCache::$app ??= Application::configure(basePath: __DIR__)->create(); +} diff --git a/tests/Integration/Routing/Fixtures/bootstrap/cache/.gitignore b/tests/Integration/Routing/Fixtures/bootstrap/cache/.gitignore new file mode 100644 index 000000000000..d6b7ef32c847 --- /dev/null +++ b/tests/Integration/Routing/Fixtures/bootstrap/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/Integration/Routing/Fixtures/cache/.gitignore b/tests/Integration/Routing/Fixtures/cache/.gitignore new file mode 100644 index 000000000000..d6b7ef32c847 --- /dev/null +++ b/tests/Integration/Routing/Fixtures/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/Routing/RouteRegistrarTest.php b/tests/Routing/RouteRegistrarTest.php index d92967a758c3..2c0c4cca9350 100644 --- a/tests/Routing/RouteRegistrarTest.php +++ b/tests/Routing/RouteRegistrarTest.php @@ -719,6 +719,8 @@ public function testCanSetShallowOptionOnRegisteredResource() public function testCanSetScopedOptionOnRegisteredResource() { $this->router->resource('users.tasks', RouteRegistrarControllerStub::class)->scoped(); + $this->router->getRoutes()->refreshNameLookups(); + $this->assertSame( ['user' => null], $this->router->getRoutes()->getByName('users.tasks.index')->bindingFields() @@ -731,6 +733,7 @@ public function testCanSetScopedOptionOnRegisteredResource() $this->router->resource('users.tasks', RouteRegistrarControllerStub::class)->scoped([ 'task' => 'slug', ]); + $this->router->getRoutes()->refreshNameLookups(); $this->assertSame( ['user' => null], $this->router->getRoutes()->getByName('users.tasks.index')->bindingFields() @@ -904,6 +907,7 @@ public function testCanSetMiddlewareForSpecifiedMethodsOnRegisteredResource() ->middlewareFor('index', RouteRegistrarMiddlewareStub::class) ->middlewareFor(['create', 'store'], 'one') ->middlewareFor(['edit'], ['one', 'two']); + $this->router->getRoutes()->refreshNameLookups(); $this->assertEquals($this->router->getRoutes()->getByName('users.index')->gatherMiddleware(), ['default', RouteRegistrarMiddlewareStub::class]); $this->assertEquals($this->router->getRoutes()->getByName('users.create')->gatherMiddleware(), ['default', 'one']); @@ -918,6 +922,7 @@ public function testCanSetMiddlewareForSpecifiedMethodsOnRegisteredResource() ->middlewareFor(['create', 'store'], 'one') ->middlewareFor(['edit'], ['one', 'two']) ->middleware('default'); + $this->router->getRoutes()->refreshNameLookups(); $this->assertEquals($this->router->getRoutes()->getByName('users.index')->gatherMiddleware(), [RouteRegistrarMiddlewareStub::class, 'default']); $this->assertEquals($this->router->getRoutes()->getByName('users.create')->gatherMiddleware(), ['one', 'default']); @@ -1448,6 +1453,7 @@ public function testCanSetMiddlewareForSpecifiedMethodsOnRegisteredSingletonReso ->middlewareFor('show', RouteRegistrarMiddlewareStub::class) ->middlewareFor(['create', 'store'], 'one') ->middlewareFor(['edit'], ['one', 'two']); + $this->router->getRoutes()->refreshNameLookups(); $this->assertEquals($this->router->getRoutes()->getByName('users.create')->gatherMiddleware(), ['default', 'one']); $this->assertEquals($this->router->getRoutes()->getByName('users.store')->gatherMiddleware(), ['default', 'one']); @@ -1463,6 +1469,7 @@ public function testCanSetMiddlewareForSpecifiedMethodsOnRegisteredSingletonReso ->middlewareFor(['create', 'store'], 'one') ->middlewareFor(['edit'], ['one', 'two']) ->middleware('default'); + $this->router->getRoutes()->refreshNameLookups(); $this->assertEquals($this->router->getRoutes()->getByName('users.create')->gatherMiddleware(), ['one', 'default']); $this->assertEquals($this->router->getRoutes()->getByName('users.store')->gatherMiddleware(), ['one', 'default']); diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index ab5c2f2150a7..dfcf5d0bd166 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -664,6 +664,13 @@ public function testItGetsAnArray() Arr::array($test_array, 'string'); } + public function testItReturnsEmptyArrayForMissingKeyByDefault() + { + $data = ['name' => 'Taylor']; + + $this->assertSame([], Arr::array($data, 'missing_key')); + } + public function testHas() { $array = ['products.desk' => ['price' => 100]]; diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index 1074e97dd6d9..fd6a28cf5c50 100644 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -1493,6 +1493,12 @@ public static function providesPregReplaceArrayData() ['/%s/', ['a', 'b', 'c'], 'Hi', 'Hi'], ['//', [], '', ''], ['/%s/', ['a'], '', ''], + // non-sequential numeric keys → should still consume in natural order + ['/%s/', [2 => 'A', 10 => 'B'], '%s %s', 'A B'], + // associative keys → order should be insertion order, not keys/pointer + ['/%s/', ['first' => 'A', 'second' => 'B'], '%s %s', 'A B'], + // values that are "falsy" but must not be treated as empty by mistake, false->'' , null->'' + ['/%s/', ['0', 0, false, null], '%s|%s|%s|%s', '0|0||'], // The internal pointer of this array is not at the beginning ['/%s/', $pointerArray, 'Hi, %s %s', 'Hi, Taylor Otwell'], ]; diff --git a/tests/Support/SupportLazyCollectionTest.php b/tests/Support/SupportLazyCollectionTest.php index 5dcedbc824c6..7c0f2a87093e 100644 --- a/tests/Support/SupportLazyCollectionTest.php +++ b/tests/Support/SupportLazyCollectionTest.php @@ -186,6 +186,8 @@ public function testTakeUntilTimeout() $mock = m::mock(LazyCollection::class.'[now]'); + $timedOutWith = []; + $results = $mock ->times(10) ->tap(function ($collection) use ($mock, $timeout) { @@ -200,10 +202,13 @@ public function testTakeUntilTimeout() $timeout->getTimestamp() ); }) - ->takeUntilTimeout($timeout) + ->takeUntilTimeout($timeout, function ($value, $key) use (&$timedOutWith) { + $timedOutWith = [$value, $key]; + }) ->all(); $this->assertSame([1, 2], $results); + $this->assertSame([2, 1], $timedOutWith); m::close(); } diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index cb36d9eb98c1..2049190e94e7 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -1357,8 +1357,8 @@ public function testWordCount() $this->assertEquals(2, Str::wordCount('Hello, world!')); $this->assertEquals(10, Str::wordCount('Hi, this is my first contribution to the Laravel framework.')); - // $this->assertEquals(0, Str::wordCount('мама')); - // $this->assertEquals(0, Str::wordCount('мама мыла раму')); + $this->assertEquals(0, Str::wordCount('мама')); + $this->assertEquals(0, Str::wordCount('мама мыла раму')); $this->assertEquals(1, Str::wordCount('мама', 'абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ')); $this->assertEquals(3, Str::wordCount('мама мыла раму', 'абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ')); diff --git a/types/Support/LazyCollection.php b/types/Support/LazyCollection.php index 4e96ba431f70..d9f49e8593aa 100644 --- a/types/Support/LazyCollection.php +++ b/types/Support/LazyCollection.php @@ -751,6 +751,10 @@ public function toArray(): array })); assertType('Illuminate\Support\LazyCollection', $collection->takeUntilTimeout(new DateTime())); +assertType('Illuminate\Support\LazyCollection', $collection->takeUntilTimeout(new DateTime(), function ($user, $int) { + assertType('User|null', $user); + // assertType('int|null', $int); +})); assertType('Illuminate\Support\LazyCollection', $collection->make([1])->takeWhile(1)); assertType('Illuminate\Support\LazyCollection', $collection->takeWhile(new User));