diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b65a09089f0..8803e41bed60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Release Notes for 12.x -## [Unreleased](https://github.com/laravel/framework/compare/v12.21.0...12.x) +## [Unreleased](https://github.com/laravel/framework/compare/v12.22.1...12.x) + +## [v12.22.1](https://github.com/laravel/framework/compare/v12.21.0...v12.22.1) - 2025-08-08 + +* [12.x] Improved assertion message by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/framework/pull/56579 +* [12.x] Fixed version increment by [@dciancu](https://github.com/dciancu) in https://github.com/laravel/framework/pull/56588 +* [12.x] Normalize file path separators in `make:migration` command on Windows by [@amirhshokri](https://github.com/amirhshokri) in https://github.com/laravel/framework/pull/56591 +* Revert "[12.x] Improve PHPDoc blocks for array of arguments in Gate" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/56593 ## [v12.21.0](https://github.com/laravel/framework/compare/v12.20.0...v12.21.0) - 2025-07-22 diff --git a/composer.json b/composer.json index 7bebb6b83513..e17d2f016bee 100644 --- a/composer.json +++ b/composer.json @@ -52,6 +52,8 @@ "symfony/mailer": "^7.2.0", "symfony/mime": "^7.2.0", "symfony/polyfill-php83": "^1.31", + "symfony/polyfill-php84": "^1.31", + "symfony/polyfill-php85": "^1.31", "symfony/process": "^7.2.0", "symfony/routing": "^7.2.0", "symfony/uid": "^7.2.0", diff --git a/src/Illuminate/Broadcasting/Broadcasters/RedisBroadcaster.php b/src/Illuminate/Broadcasting/Broadcasters/RedisBroadcaster.php index 9cb81c85af1d..15878575dec0 100644 --- a/src/Illuminate/Broadcasting/Broadcasters/RedisBroadcaster.php +++ b/src/Illuminate/Broadcasting/Broadcasters/RedisBroadcaster.php @@ -4,7 +4,11 @@ use Illuminate\Broadcasting\BroadcastException; use Illuminate\Contracts\Redis\Factory as Redis; +use Illuminate\Redis\Connections\PhpRedisClusterConnection; +use Illuminate\Redis\Connections\PredisClusterConnection; +use Illuminate\Redis\Connections\PredisConnection; use Illuminate\Support\Arr; +use Predis\Connection\Cluster\RedisCluster; use Predis\Connection\ConnectionException; use RedisException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -125,10 +129,30 @@ public function broadcast(array $channels, $event, array $payload = []) ]); try { - $connection->eval( - $this->broadcastMultipleChannelsScript(), - 0, $payload, ...$this->formatChannels($channels) - ); + if ($connection instanceof PhpRedisClusterConnection) { + foreach ($channels as $channel) { + $connection->publish($channel, $payload); + } + } elseif ($connection instanceof PredisClusterConnection && + $connection->client()->getConnection() instanceof RedisCluster) { + $randomClusterNodeConnection = new PredisConnection( + $connection->client()->getClientBy('slot', mt_rand(0, 16383)) + ); + + if ($events = $connection->getEventDispatcher()) { + $randomClusterNodeConnection->setEventDispatcher($events); + } + + $randomClusterNodeConnection->eval( + $this->broadcastMultipleChannelsScript(), + 0, $payload, ...$this->formatChannels($channels) + ); + } else { + $connection->eval( + $this->broadcastMultipleChannelsScript(), + 0, $payload, ...$this->formatChannels($channels) + ); + } } catch (ConnectionException|RedisException $e) { throw new BroadcastException( sprintf('Redis error: %s.', $e->getMessage()) diff --git a/src/Illuminate/Cache/RateLimiter.php b/src/Illuminate/Cache/RateLimiter.php index 67588ffbb1e1..f28c57a4d240 100644 --- a/src/Illuminate/Cache/RateLimiter.php +++ b/src/Illuminate/Cache/RateLimiter.php @@ -210,7 +210,7 @@ public function attempts($key) * Reset the number of attempts for the given key. * * @param string $key - * @return mixed + * @return bool */ public function resetAttempts($key) { diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index a6273b46043a..c9b4defde497 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -263,13 +263,9 @@ public static function first($array, ?callable $callback = null, $default = null return value($default); } - foreach ($array as $key => $value) { - if ($callback($value, $key)) { - return $value; - } - } + $key = array_find_key($array, $callback); - return value($default); + return $key !== null ? $array[$key] : value($default); } /** diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index ce683bee94d1..9213ba3c377e 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -184,9 +184,7 @@ public function contains($key, $operator = null, $value = null) { if (func_num_args() === 1) { if ($this->useAsCallable($key)) { - $placeholder = new stdClass; - - return $this->first($key, $placeholder) !== $placeholder; + return array_any($this->items, $key); } return in_array($key, $this->items); @@ -458,8 +456,7 @@ public function flip() /** * Remove an item from the collection by key. * - * \Illuminate\Contracts\Support\Arrayable|iterable|TKey $keys - * + * @param \Illuminate\Contracts\Support\Arrayable|iterable|TKey $keys * @return $this */ public function forget($keys) @@ -599,13 +596,7 @@ public function has($key) { $keys = is_array($key) ? $key : func_get_args(); - foreach ($keys as $value) { - if (! array_key_exists($value, $this->items)) { - return false; - } - } - - return true; + return array_all($keys, fn ($key) => array_key_exists($key, $this->items)); } /** @@ -622,13 +613,7 @@ public function hasAny($key) $keys = is_array($key) ? $key : func_get_args(); - foreach ($keys as $value) { - if (array_key_exists($value, $this->items)) { - return true; - } - } - - return false; + return array_any($keys, fn ($key) => array_key_exists($key, $this->items)); } /** @@ -1005,7 +990,7 @@ public function select($keys) * Get and remove the last N items from the collection. * * @param int $count - * @return static|TValue|null + * @return ($count is 1 ? TValue|null : static) */ public function pop($count = 1) { @@ -1127,7 +1112,7 @@ public function put($key, $value) * * @param (callable(self): int)|int|null $number * @param bool $preserveKeys - * @return static|TValue + * @return ($number is null ? TValue : static) * * @throws \InvalidArgumentException */ @@ -1189,13 +1174,7 @@ public function search($value, $strict = false) return array_search($value, $this->items, $strict); } - foreach ($this->items as $key => $item) { - if ($value($item, $key)) { - return $key; - } - } - - return false; + return array_find_key($this->items, $value) ?? false; } /** @@ -1250,7 +1229,7 @@ public function after($value, $strict = false) * Get and remove the first N items from the collection. * * @param int<0, max> $count - * @return static|TValue|null + * @return ($count is 1 ? TValue|null : static) * * @throws \InvalidArgumentException */ diff --git a/src/Illuminate/Collections/composer.json b/src/Illuminate/Collections/composer.json index 8d9c96125a47..bf7565d3a4ec 100644 --- a/src/Illuminate/Collections/composer.json +++ b/src/Illuminate/Collections/composer.json @@ -17,7 +17,8 @@ "php": "^8.2", "illuminate/conditionable": "^12.0", "illuminate/contracts": "^12.0", - "illuminate/macroable": "^12.0" + "illuminate/macroable": "^12.0", + "symfony/polyfill-php84": "^1.31" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Container/Attributes/Bind.php b/src/Illuminate/Container/Attributes/Bind.php index 3a9013546310..92bae25479fc 100644 --- a/src/Illuminate/Container/Attributes/Bind.php +++ b/src/Illuminate/Container/Attributes/Bind.php @@ -28,13 +28,13 @@ class Bind * Create a new attribute instance. * * @param class-string $concrete - * @param non-empty-array|non-empty-string $environments + * @param non-empty-array|non-empty-string|\UnitEnum $environments * * @throws \InvalidArgumentException */ public function __construct( string $concrete, - string|array $environments = ['*'], + string|array|UnitEnum $environments = ['*'], ) { $environments = array_filter(is_array($environments) ? $environments : [$environments]); diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index 04018c8f9151..b71c5204b79b 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -281,21 +281,36 @@ public function isShared($abstract) return false; } - $reflection = new ReflectionClass($abstract); - - if (! empty($reflection->getAttributes(Singleton::class))) { - return true; + if (($scopedType = $this->getScopedTyped(new ReflectionClass($abstract))) === null) { + return false; } - if (! empty($reflection->getAttributes(Scoped::class))) { + if ($scopedType === 'scoped') { if (! in_array($abstract, $this->scopedInstances, true)) { $this->scopedInstances[] = $abstract; } + } - return true; + return true; + } + + /** + * Determine if a ReflectionClass has scoping attributes applied. + * + * @param ReflectionClass $reflection + * @return "singleton"|"scoped"|null + */ + protected function getScopedTyped(ReflectionClass $reflection): ?string + { + if (! empty($reflection->getAttributes(Singleton::class))) { + return 'singleton'; } - return false; + if (! empty($reflection->getAttributes(Scoped::class))) { + return 'scoped'; + } + + return null; } /** @@ -983,34 +998,34 @@ protected function getConcrete($abstract) return $abstract; } - $attributes = []; - - try { - $attributes = (new ReflectionClass($abstract))->getAttributes(Bind::class); - } catch (ReflectionException) { - } - - $this->checkedForAttributeBindings[$abstract] = true; - - if ($attributes === []) { - return $abstract; - } - - return $this->getConcreteBindingFromAttributes($abstract, $attributes); + return $this->getConcreteBindingFromAttributes($abstract); } /** * Get the concrete binding for an abstract from the Bind attribute. * * @param string $abstract - * @param array> $reflectedAttributes * @return mixed */ - protected function getConcreteBindingFromAttributes($abstract, $reflectedAttributes) + protected function getConcreteBindingFromAttributes($abstract) { + $this->checkedForAttributeBindings[$abstract] = true; + + try { + $reflected = new ReflectionClass($abstract); + } catch (ReflectionException) { + return $abstract; + } + + $bindAttributes = $reflected->getAttributes(Bind::class); + + if ($bindAttributes === []) { + return $abstract; + } + $concrete = $maybeConcrete = null; - foreach ($reflectedAttributes as $reflectedAttribute) { + foreach ($bindAttributes as $reflectedAttribute) { $instance = $reflectedAttribute->newInstance(); if ($instance->environments === ['*']) { @@ -1034,7 +1049,11 @@ protected function getConcreteBindingFromAttributes($abstract, $reflectedAttribu return $abstract; } - $this->bind($abstract, $concrete); + match ($this->getScopedTyped($reflected)) { + 'scoped' => $this->scoped($abstract, $concrete), + 'singleton' => $this->singleton($abstract, $concrete), + null => $this->bind($abstract, $concrete), + }; return $this->bindings[$abstract]['concrete']; } diff --git a/src/Illuminate/Database/Console/Migrations/TableGuesser.php b/src/Illuminate/Database/Console/Migrations/TableGuesser.php index 30bd53096e06..2c90387b06fc 100644 --- a/src/Illuminate/Database/Console/Migrations/TableGuesser.php +++ b/src/Illuminate/Database/Console/Migrations/TableGuesser.php @@ -14,6 +14,11 @@ class TableGuesser '/.+_(to|from|in)_(\w+)$/', ]; + const DROP_PATTERNS = [ + '/^drop_(\w+)_table$/', + '/^drop_(\w+)$/', + ]; + /** * Attempt to guess the table name and "creation" status of the given migration. * @@ -33,5 +38,11 @@ public static function guess($migration) return [$matches[2], $create = false]; } } + + foreach (self::DROP_PATTERNS as $pattern) { + if (preg_match($pattern, $migration, $matches)) { + return [$matches[1], $create = false]; + } + } } } diff --git a/src/Illuminate/Database/LostConnectionDetector.php b/src/Illuminate/Database/LostConnectionDetector.php index 0e0ce0d50d4b..921475a09a65 100644 --- a/src/Illuminate/Database/LostConnectionDetector.php +++ b/src/Illuminate/Database/LostConnectionDetector.php @@ -83,6 +83,12 @@ public function causedByLostConnection(Throwable $e): bool 'Connection lost', 'Broken pipe', 'SQLSTATE[25006]: Read only sql transaction: 7', + 'vtgate connection error: no healthy endpoints', + 'primary is not serving, there may be a reparent operation in progress', + 'current keyspace is being resharded', + 'no healthy tablet available', + 'transaction pool connection limit exceeded', + 'SSL operation failed with code 5', ]); } } diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index 0f83b5f03a8f..5be4561bf046 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -1485,16 +1485,17 @@ public function vector($column, $dimensions = null) * * @param string $name * @param string|null $indexName + * @param string|null $after * @return void */ - public function morphs($name, $indexName = null) + public function morphs($name, $indexName = null, $after = null) { if (Builder::$defaultMorphKeyType === 'uuid') { - $this->uuidMorphs($name, $indexName); + $this->uuidMorphs($name, $indexName, $after); } elseif (Builder::$defaultMorphKeyType === 'ulid') { - $this->ulidMorphs($name, $indexName); + $this->ulidMorphs($name, $indexName, $after); } else { - $this->numericMorphs($name, $indexName); + $this->numericMorphs($name, $indexName, $after); } } @@ -1503,16 +1504,17 @@ public function morphs($name, $indexName = null) * * @param string $name * @param string|null $indexName + * @param string|null $after * @return void */ - public function nullableMorphs($name, $indexName = null) + public function nullableMorphs($name, $indexName = null, $after = null) { if (Builder::$defaultMorphKeyType === 'uuid') { - $this->nullableUuidMorphs($name, $indexName); + $this->nullableUuidMorphs($name, $indexName, $after); } elseif (Builder::$defaultMorphKeyType === 'ulid') { - $this->nullableUlidMorphs($name, $indexName); + $this->nullableUlidMorphs($name, $indexName, $after); } else { - $this->nullableNumericMorphs($name, $indexName); + $this->nullableNumericMorphs($name, $indexName, $after); } } @@ -1521,13 +1523,16 @@ public function nullableMorphs($name, $indexName = null) * * @param string $name * @param string|null $indexName + * @param string|null $after * @return void */ - public function numericMorphs($name, $indexName = null) + public function numericMorphs($name, $indexName = null, $after = null) { - $this->string("{$name}_type"); + $this->string("{$name}_type") + ->after($after); - $this->unsignedBigInteger("{$name}_id"); + $this->unsignedBigInteger("{$name}_id") + ->after(! is_null($after) ? "{$name}_type" : null); $this->index(["{$name}_type", "{$name}_id"], $indexName); } @@ -1537,13 +1542,18 @@ public function numericMorphs($name, $indexName = null) * * @param string $name * @param string|null $indexName + * @param string|null $after * @return void */ - public function nullableNumericMorphs($name, $indexName = null) + public function nullableNumericMorphs($name, $indexName = null, $after = null) { - $this->string("{$name}_type")->nullable(); + $this->string("{$name}_type") + ->nullable() + ->after($after); - $this->unsignedBigInteger("{$name}_id")->nullable(); + $this->unsignedBigInteger("{$name}_id") + ->nullable() + ->after(! is_null($after) ? "{$name}_type" : null); $this->index(["{$name}_type", "{$name}_id"], $indexName); } @@ -1553,13 +1563,16 @@ public function nullableNumericMorphs($name, $indexName = null) * * @param string $name * @param string|null $indexName + * @param string|null $after * @return void */ - public function uuidMorphs($name, $indexName = null) + public function uuidMorphs($name, $indexName = null, $after = null) { - $this->string("{$name}_type"); + $this->string("{$name}_type") + ->after($after); - $this->uuid("{$name}_id"); + $this->uuid("{$name}_id") + ->after(! is_null($after) ? "{$name}_type" : null); $this->index(["{$name}_type", "{$name}_id"], $indexName); } @@ -1569,13 +1582,18 @@ public function uuidMorphs($name, $indexName = null) * * @param string $name * @param string|null $indexName + * @param string|null $after * @return void */ - public function nullableUuidMorphs($name, $indexName = null) + public function nullableUuidMorphs($name, $indexName = null, $after = null) { - $this->string("{$name}_type")->nullable(); + $this->string("{$name}_type") + ->nullable() + ->after($after); - $this->uuid("{$name}_id")->nullable(); + $this->uuid("{$name}_id") + ->nullable() + ->after(! is_null($after) ? "{$name}_type" : null); $this->index(["{$name}_type", "{$name}_id"], $indexName); } @@ -1585,13 +1603,16 @@ public function nullableUuidMorphs($name, $indexName = null) * * @param string $name * @param string|null $indexName + * @param string|null $after * @return void */ - public function ulidMorphs($name, $indexName = null) + public function ulidMorphs($name, $indexName = null, $after = null) { - $this->string("{$name}_type"); + $this->string("{$name}_type") + ->after($after); - $this->ulid("{$name}_id"); + $this->ulid("{$name}_id") + ->after(! is_null($after) ? "{$name}_type" : null); $this->index(["{$name}_type", "{$name}_id"], $indexName); } @@ -1601,13 +1622,18 @@ public function ulidMorphs($name, $indexName = null) * * @param string $name * @param string|null $indexName + * @param string|null $after * @return void */ - public function nullableUlidMorphs($name, $indexName = null) + public function nullableUlidMorphs($name, $indexName = null, $after = null) { - $this->string("{$name}_type")->nullable(); + $this->string("{$name}_type") + ->nullable() + ->after($after); - $this->ulid("{$name}_id")->nullable(); + $this->ulid("{$name}_id") + ->nullable() + ->after(! is_null($after) ? "{$name}_type" : null); $this->index(["{$name}_type", "{$name}_id"], $indexName); } diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index 708e75058d1f..fa95e2a50fd9 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -323,7 +323,7 @@ public function compilePrimary(Blueprint $blueprint, Fluent $command) * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command - * @return string + * @return string[] */ public function compileUnique(Blueprint $blueprint, Fluent $command) { @@ -333,12 +333,29 @@ public function compileUnique(Blueprint $blueprint, Fluent $command) $uniqueStatement .= ' nulls '.($command->nullsNotDistinct ? 'not distinct' : 'distinct'); } - $sql = sprintf('alter table %s add constraint %s %s (%s)', - $this->wrapTable($blueprint), - $this->wrap($command->index), - $uniqueStatement, - $this->columnize($command->columns) - ); + if ($command->online || $command->algorithm) { + $createIndexSql = sprintf('create unique index %s%s on %s%s (%s)', + $command->online ? 'concurrently ' : '', + $this->wrap($command->index), + $this->wrapTable($blueprint), + $command->algorithm ? ' using '.$command->algorithm : '', + $this->columnize($command->columns) + ); + + $sql = sprintf('alter table %s add constraint %s unique using index %s', + $this->wrapTable($blueprint), + $this->wrap($command->index), + $this->wrap($command->index) + ); + } else { + $sql = sprintf( + 'alter table %s add constraint %s %s (%s)', + $this->wrapTable($blueprint), + $this->wrap($command->index), + $uniqueStatement, + $this->columnize($command->columns) + ); + } if (! is_null($command->deferrable)) { $sql .= $command->deferrable ? ' deferrable' : ' not deferrable'; @@ -348,7 +365,7 @@ public function compileUnique(Blueprint $blueprint, Fluent $command) $sql .= $command->initiallyImmediate ? ' initially immediate' : ' initially deferred'; } - return $sql; + return isset($createIndexSql) ? [$createIndexSql, $sql] : [$sql]; } /** @@ -360,7 +377,8 @@ public function compileUnique(Blueprint $blueprint, Fluent $command) */ public function compileIndex(Blueprint $blueprint, Fluent $command) { - return sprintf('create index %s on %s%s (%s)', + return sprintf('create index %s%s on %s%s (%s)', + $command->online ? 'concurrently ' : '', $this->wrap($command->index), $this->wrapTable($blueprint), $command->algorithm ? ' using '.$command->algorithm : '', @@ -385,7 +403,8 @@ public function compileFulltext(Blueprint $blueprint, Fluent $command) return "to_tsvector({$this->quoteString($language)}, {$this->wrap($column)})"; }, $command->columns); - return sprintf('create index %s on %s using gin ((%s))', + return sprintf('create index %s%s on %s using gin ((%s))', + $command->online ? 'concurrently ' : '', $this->wrap($command->index), $this->wrapTable($blueprint), implode(' || ', $columns) @@ -421,7 +440,8 @@ protected function compileIndexWithOperatorClass(Blueprint $blueprint, Fluent $c { $columns = $this->columnizeWithOperatorClass($command->columns, $command->operatorClass); - return sprintf('create index %s on %s%s (%s)', + return sprintf('create index %s%s on %s%s (%s)', + $command->online ? 'concurrently ' : '', $this->wrap($command->index), $this->wrapTable($blueprint), $command->algorithm ? ' using '.$command->algorithm : '', diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index f0608eb2e4dc..28b5e5a7a161 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -272,10 +272,11 @@ public function compilePrimary(Blueprint $blueprint, Fluent $command) */ public function compileUnique(Blueprint $blueprint, Fluent $command) { - return sprintf('create unique index %s on %s (%s)', + return sprintf('create unique index %s on %s (%s)%s', $this->wrap($command->index), $this->wrapTable($blueprint), - $this->columnize($command->columns) + $this->columnize($command->columns), + $command->online ? ' with (online = on)' : '' ); } @@ -288,10 +289,11 @@ public function compileUnique(Blueprint $blueprint, Fluent $command) */ public function compileIndex(Blueprint $blueprint, Fluent $command) { - return sprintf('create index %s on %s (%s)', + return sprintf('create index %s on %s (%s)%s', $this->wrap($command->index), $this->wrapTable($blueprint), - $this->columnize($command->columns) + $this->columnize($command->columns), + $command->online ? ' with (online = on)' : '' ); } diff --git a/src/Illuminate/Database/Schema/IndexDefinition.php b/src/Illuminate/Database/Schema/IndexDefinition.php index d11a3c8daeed..96dba99ee566 100644 --- a/src/Illuminate/Database/Schema/IndexDefinition.php +++ b/src/Illuminate/Database/Schema/IndexDefinition.php @@ -10,6 +10,7 @@ * @method $this deferrable(bool $value = true) Specify that the unique index is deferrable (PostgreSQL) * @method $this initiallyImmediate(bool $value = true) Specify the default time to check the unique index constraint (PostgreSQL) * @method $this nullsNotDistinct(bool $value = true) Specify that the null values should not be treated as distinct (PostgreSQL) + * @method $this online(bool $value = true) Specify that index creation should not lock the table (PostgreSQL/SqlServer) */ class IndexDefinition extends Fluent { diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 63da4578cfce..7868a6edeb5b 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '12.22.1'; + const VERSION = '12.23.0'; /** * The base path for the Laravel installation. diff --git a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php index 90bc446dca4e..a4167ee9c609 100644 --- a/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php +++ b/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php @@ -326,27 +326,11 @@ public static function flushState() */ public static function flushHandlersState() { - while (true) { - $previousHandler = set_exception_handler(static fn () => null); - - restore_exception_handler(); - - if ($previousHandler === null) { - break; - } - + while (get_exception_handler() !== null) { restore_exception_handler(); } - while (true) { - $previousHandler = set_error_handler(static fn () => null); - - restore_error_handler(); - - if ($previousHandler === null) { - break; - } - + while (get_error_handler() !== null) { restore_error_handler(); } diff --git a/src/Illuminate/Http/composer.json b/src/Illuminate/Http/composer.json index 0ab2371d7c50..9681e6060ec4 100755 --- a/src/Illuminate/Http/composer.json +++ b/src/Illuminate/Http/composer.json @@ -26,6 +26,7 @@ "symfony/http-foundation": "^7.2.0", "symfony/http-kernel": "^7.2.0", "symfony/polyfill-php83": "^1.31", + "symfony/polyfill-php85": "^1.31", "symfony/mime": "^7.2.0" }, "autoload": { diff --git a/src/Illuminate/Mail/Transport/ResendTransport.php b/src/Illuminate/Mail/Transport/ResendTransport.php index 9693eaf3a476..47c511f536c9 100644 --- a/src/Illuminate/Mail/Transport/ResendTransport.php +++ b/src/Illuminate/Mail/Transport/ResendTransport.php @@ -73,7 +73,7 @@ protected function doSend(SentMessage $message): void foreach ($email->getAttachments() as $attachment) { $attachmentHeaders = $attachment->getPreparedHeaders(); $contentType = $attachmentHeaders->get('Content-Type')->getBody(); - + $disposition = $attachmentHeaders->getHeaderBody('Content-Disposition'); $filename = $attachmentHeaders->getHeaderParameter('Content-Disposition', 'filename'); if ($contentType == 'text/calendar') { @@ -88,6 +88,10 @@ protected function doSend(SentMessage $message): void 'filename' => $filename, ]; + if ($disposition === 'inline') { + $item['inline_content_id'] = $attachment->hasContentId() ? $attachment->getContentId() : $filename; + } + $attachments[] = $item; } } diff --git a/src/Illuminate/Pagination/LengthAwarePaginator.php b/src/Illuminate/Pagination/LengthAwarePaginator.php index 2a6fd8d2149c..40240afe7635 100644 --- a/src/Illuminate/Pagination/LengthAwarePaginator.php +++ b/src/Illuminate/Pagination/LengthAwarePaginator.php @@ -121,16 +121,19 @@ public function linkCollection() return [ 'url' => $url, 'label' => (string) $page, + 'page' => $page, 'active' => $this->currentPage() === $page, ]; }); })->prepend([ 'url' => $this->previousPageUrl(), 'label' => function_exists('__') ? __('pagination.previous') : 'Previous', + 'page' => $this->currentPage() > 1 ? $this->currentPage() - 1 : null, 'active' => false, ])->push([ 'url' => $this->nextPageUrl(), 'label' => function_exists('__') ? __('pagination.next') : 'Next', + 'page' => $this->hasMorePages() ? $this->currentPage() + 1 : null, 'active' => false, ]); } diff --git a/src/Illuminate/Support/Facades/Facade.php b/src/Illuminate/Support/Facades/Facade.php index 8874954a919b..1e643a3f9101 100755 --- a/src/Illuminate/Support/Facades/Facade.php +++ b/src/Illuminate/Support/Facades/Facade.php @@ -5,6 +5,7 @@ use Closure; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; +use Illuminate\Support\Benchmark; use Illuminate\Support\Collection; use Illuminate\Support\Js; use Illuminate\Support\Number; @@ -276,6 +277,7 @@ public static function defaultAliases() 'Arr' => Arr::class, 'Artisan' => Artisan::class, 'Auth' => Auth::class, + 'Benchmark' => Benchmark::class, 'Blade' => Blade::class, 'Broadcast' => Broadcast::class, 'Bus' => Bus::class, diff --git a/src/Illuminate/Support/Facades/RateLimiter.php b/src/Illuminate/Support/Facades/RateLimiter.php index 7f8cf5c2166c..e04a4d1630b3 100644 --- a/src/Illuminate/Support/Facades/RateLimiter.php +++ b/src/Illuminate/Support/Facades/RateLimiter.php @@ -11,7 +11,7 @@ * @method static int increment(string $key, \DateTimeInterface|\DateInterval|int $decaySeconds = 60, int $amount = 1) * @method static int decrement(string $key, \DateTimeInterface|\DateInterval|int $decaySeconds = 60, int $amount = 1) * @method static mixed attempts(string $key) - * @method static mixed resetAttempts(string $key) + * @method static bool resetAttempts(string $key) * @method static int remaining(string $key, int $maxAttempts) * @method static int retriesLeft(string $key, int $maxAttempts) * @method static void clear(string $key) diff --git a/src/Illuminate/Support/Sleep.php b/src/Illuminate/Support/Sleep.php index 9b761811cfb9..b6b6ab32bca2 100644 --- a/src/Illuminate/Support/Sleep.php +++ b/src/Illuminate/Support/Sleep.php @@ -428,29 +428,37 @@ public static function assertSleptTimes($expected) */ public static function assertSequence($sequence) { - static::assertSleptTimes(count($sequence)); - - (new Collection($sequence)) - ->zip(static::$sequence) - ->eachSpread(function (?Sleep $expected, CarbonInterval $actual) { - if ($expected === null) { - return; + try { + static::assertSleptTimes(count($sequence)); + + (new Collection($sequence)) + ->zip(static::$sequence) + ->eachSpread(function (?Sleep $expected, CarbonInterval $actual) { + if ($expected === null) { + return; + } + + PHPUnit::assertTrue( + $expected->shouldNotSleep()->duration->equalTo($actual), + vsprintf('Expected sleep duration of [%s] but actually slept for [%s].', [ + $expected->duration->cascade()->forHumans([ + 'options' => 0, + 'minimumUnit' => 'microsecond', + ]), + $actual->cascade()->forHumans([ + 'options' => 0, + 'minimumUnit' => 'microsecond', + ]), + ]) + ); + }); + } finally { + foreach ($sequence as $expected) { + if ($expected instanceof self) { + $expected->shouldNotSleep(); } - - PHPUnit::assertTrue( - $expected->shouldNotSleep()->duration->equalTo($actual), - vsprintf('Expected sleep duration of [%s] but actually slept for [%s].', [ - $expected->duration->cascade()->forHumans([ - 'options' => 0, - 'minimumUnit' => 'microsecond', - ]), - $actual->cascade()->forHumans([ - 'options' => 0, - 'minimumUnit' => 'microsecond', - ]), - ]) - ); - }); + } + } } /** diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 8f0c98585794..e182be45f7f6 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -4,7 +4,6 @@ use Closure; use Illuminate\Support\Traits\Macroable; -use JsonException; use League\CommonMark\Environment\Environment; use League\CommonMark\Extension\GithubFlavoredMarkdownExtension; use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension; @@ -577,17 +576,7 @@ public static function isJson($value) return false; } - if (function_exists('json_validate')) { - return json_validate($value, 512); - } - - try { - json_decode($value, true, 512, JSON_THROW_ON_ERROR); - } catch (JsonException) { - return false; - } - - return true; + return json_validate($value, 512); } /** @@ -937,17 +926,7 @@ public static function numbers($value) */ public static function padBoth($value, $length, $pad = ' ') { - if (function_exists('mb_str_pad')) { - return mb_str_pad($value, $length, $pad, STR_PAD_BOTH); - } - - $short = max(0, $length - mb_strlen($value)); - $shortLeft = floor($short / 2); - $shortRight = ceil($short / 2); - - return mb_substr(str_repeat($pad, $shortLeft), 0, $shortLeft). - $value. - mb_substr(str_repeat($pad, $shortRight), 0, $shortRight); + return mb_str_pad($value, $length, $pad, STR_PAD_BOTH); } /** @@ -960,13 +939,7 @@ public static function padBoth($value, $length, $pad = ' ') */ public static function padLeft($value, $length, $pad = ' ') { - if (function_exists('mb_str_pad')) { - return mb_str_pad($value, $length, $pad, STR_PAD_LEFT); - } - - $short = max(0, $length - mb_strlen($value)); - - return mb_substr(str_repeat($pad, $short), 0, $short).$value; + return mb_str_pad($value, $length, $pad, STR_PAD_LEFT); } /** @@ -979,13 +952,7 @@ public static function padLeft($value, $length, $pad = ' ') */ public static function padRight($value, $length, $pad = ' ') { - if (function_exists('mb_str_pad')) { - return mb_str_pad($value, $length, $pad, STR_PAD_RIGHT); - } - - $short = max(0, $length - mb_strlen($value)); - - return $value.mb_substr(str_repeat($pad, $short), 0, $short); + return mb_str_pad($value, $length, $pad, STR_PAD_RIGHT); } /** diff --git a/src/Illuminate/Support/composer.json b/src/Illuminate/Support/composer.json index 404eff091464..6a1e5fcbffea 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -24,6 +24,7 @@ "illuminate/contracts": "^12.0", "illuminate/macroable": "^12.0", "nesbot/carbon": "^3.8.4", + "symfony/polyfill-php83": "^1.31", "voku/portable-ascii": "^2.0.2" }, "conflict": { diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 92a5f68216b2..77bcb0259c43 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -1757,11 +1757,9 @@ public function ddBody($key = null) { $content = $this->content(); - if (function_exists('json_validate') && json_validate($content)) { - $this->ddJson($key); - } - - dd($content); + return json_validate($content) + ? $this->ddJson($key) + : dd($content); } /** diff --git a/src/Illuminate/Testing/composer.json b/src/Illuminate/Testing/composer.json index 0f4286aba64c..4f556b831ba6 100644 --- a/src/Illuminate/Testing/composer.json +++ b/src/Illuminate/Testing/composer.json @@ -19,7 +19,8 @@ "illuminate/collections": "^12.0", "illuminate/contracts": "^12.0", "illuminate/macroable": "^12.0", - "illuminate/support": "^12.0" + "illuminate/support": "^12.0", + "symfony/polyfill-php83": "^1.31" }, "autoload": { "psr-4": { diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index 5d4f32ec6438..c773609055a2 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -1631,13 +1631,7 @@ public function validateJson($attribute, $value) return false; } - if (function_exists('json_validate')) { - return json_validate($value); - } - - json_decode($value); - - return json_last_error() === JSON_ERROR_NONE; + return json_validate($value); } /** diff --git a/src/Illuminate/Validation/Rules/RequiredIf.php b/src/Illuminate/Validation/Rules/RequiredIf.php index d3991ad387de..a15b4c45c3b0 100644 --- a/src/Illuminate/Validation/Rules/RequiredIf.php +++ b/src/Illuminate/Validation/Rules/RequiredIf.php @@ -18,10 +18,14 @@ class RequiredIf implements Stringable /** * Create a new required validation rule based on a condition. * - * @param (\Closure(): bool)|bool $condition + * @param (\Closure(): bool)|bool|null $condition */ public function __construct($condition) { + if (is_null($condition)) { + $condition = false; + } + if ($condition instanceof Closure || is_bool($condition)) { $this->condition = $condition; } else { diff --git a/src/Illuminate/Validation/composer.json b/src/Illuminate/Validation/composer.json index 5494323cf1f2..cf721bd9f4fd 100755 --- a/src/Illuminate/Validation/composer.json +++ b/src/Illuminate/Validation/composer.json @@ -26,7 +26,8 @@ "illuminate/support": "^12.0", "illuminate/translation": "^12.0", "symfony/http-foundation": "^7.2", - "symfony/mime": "^7.2" + "symfony/mime": "^7.2", + "symfony/polyfill-php83": "^1.31" }, "autoload": { "psr-4": { diff --git a/tests/Container/ContainerTest.php b/tests/Container/ContainerTest.php index 0cef966740e5..fd97e4036b02 100755 --- a/tests/Container/ContainerTest.php +++ b/tests/Container/ContainerTest.php @@ -875,6 +875,30 @@ public function testNoMatchingEnvironmentAndNoWildcardThrowsBindingResolutionExc $container->make(ProdEnvOnlyInterface::class); } + public function testScopedSingletonWithBind() + { + $container = new Container; + $container->resolveEnvironmentUsing(fn ($environments) => true); + + $original = $container->make(IsScoped::class); + $new = $container->make(IsScoped::class); + + $this->assertSame($original, $new); + $container->forgetScopedInstances(); + $this->assertNotSame($original, $container->make(IsScoped::class)); + } + + public function testSingletonWithBind() + { + $container = new Container; + $container->resolveEnvironmentUsing(fn ($environments) => true); + + $original = $container->make(IsSingleton::class); + $new = $container->make(IsSingleton::class); + + $this->assertSame($original, $new); + } + // public function testContainerCanCatchCircularDependency() // { // $this->expectException(\Illuminate\Contracts\Container\CircularDependencyException::class); @@ -1131,3 +1155,19 @@ class OriginalConcrete implements OverrideInterface class AltConcrete implements OverrideInterface { } + +#[Bind(IsScopedConcrete::class)] +#[Scoped] +interface IsScoped +{ +} + +class IsScopedConcrete implements IsScoped +{ +} + +#[Bind(IsScopedConcrete::class)] +#[Singleton] +interface IsSingleton +{ +} diff --git a/tests/Database/DatabasePostgresSchemaGrammarTest.php b/tests/Database/DatabasePostgresSchemaGrammarTest.php index aba2f2ce7bc8..cf2ea7d63f4a 100755 --- a/tests/Database/DatabasePostgresSchemaGrammarTest.php +++ b/tests/Database/DatabasePostgresSchemaGrammarTest.php @@ -307,6 +307,17 @@ public function testAddingUniqueKeyWithNullsDistinct() $this->assertSame('alter table "users" add constraint "bar" unique nulls distinct ("foo")', $statements[0]); } + public function testAddingUniqueKeyOnline() + { + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->unique('foo')->online(); + $statements = $blueprint->toSql(); + + $this->assertCount(2, $statements); + $this->assertSame('create unique index concurrently "users_foo_unique" on "users" ("foo")', $statements[0]); + $this->assertSame('alter table "users" add constraint "users_foo_unique" unique using index "users_foo_unique"', $statements[1]); + } + public function testAddingIndex() { $blueprint = new Blueprint($this->getConnection(), 'users'); @@ -327,6 +338,16 @@ public function testAddingIndexWithAlgorithm() $this->assertSame('create index "baz" on "users" using hash ("foo", "bar")', $statements[0]); } + public function testAddingIndexOnline() + { + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->index('foo', 'baz')->online(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('create index concurrently "baz" on "users" ("foo")', $statements[0]); + } + public function testAddingFulltextIndex() { $blueprint = new Blueprint($this->getConnection(), 'users'); @@ -357,6 +378,16 @@ public function testAddingFulltextIndexWithLanguage() $this->assertSame('create index "users_body_fulltext" on "users" using gin ((to_tsvector(\'spanish\', "body")))', $statements[0]); } + public function testAddingFulltextIndexOnline() + { + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->fulltext('body')->online(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('create index concurrently "users_body_fulltext" on "users" using gin ((to_tsvector(\'english\', "body")))', $statements[0]); + } + public function testAddingFulltextIndexWithFluency() { $blueprint = new Blueprint($this->getConnection(), 'users'); @@ -377,6 +408,16 @@ public function testAddingSpatialIndex() $this->assertSame('create index "geo_coordinates_spatialindex" on "geo" using gist ("coordinates")', $statements[0]); } + public function testAddingSpatialIndexOnline() + { + $blueprint = new Blueprint($this->getConnection(), 'geo'); + $blueprint->spatialIndex('coordinates')->online(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('create index concurrently "geo_coordinates_spatialindex" on "geo" using gist ("coordinates")', $statements[0]); + } + public function testAddingFluentSpatialIndex() { $blueprint = new Blueprint($this->getConnection(), 'geo'); @@ -407,6 +448,16 @@ public function testAddingSpatialIndexWithOperatorClassMultipleColumns() $this->assertSame('create index "my_index" on "geo" using gist ("coordinates" point_ops, "location" point_ops)', $statements[0]); } + public function testAddingSpatialIndexWithOperatorClassOnline() + { + $blueprint = new Blueprint($this->getConnection(), 'geo'); + $blueprint->spatialIndex('coordinates', 'my_index', 'point_ops')->online(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('create index concurrently "my_index" on "geo" using gist ("coordinates" point_ops)', $statements[0]); + } + public function testAddingRawIndex() { $blueprint = new Blueprint($this->getConnection(), 'users'); @@ -417,6 +468,16 @@ public function testAddingRawIndex() $this->assertSame('create index "raw_index" on "users" ((function(column)))', $statements[0]); } + public function testAddingRawIndexOnline() + { + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->rawIndex('(function(column))', 'raw_index')->online(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('create index concurrently "raw_index" on "users" ((function(column)))', $statements[0]); + } + public function testAddingIncrementingID() { $blueprint = new Blueprint($this->getConnection(), 'users'); diff --git a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php index de87d7477fc9..0e2dbafd5e2d 100755 --- a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php +++ b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php @@ -283,6 +283,16 @@ public function testAddingUniqueKey() $this->assertSame('create unique index "bar" on "users" ("foo")', $statements[0]); } + public function testAddingUniqueKeyOnline() + { + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->unique('foo', 'bar')->online(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('create unique index "bar" on "users" ("foo") with (online = on)', $statements[0]); + } + public function testAddingIndex() { $blueprint = new Blueprint($this->getConnection(), 'users'); @@ -293,6 +303,16 @@ public function testAddingIndex() $this->assertSame('create index "baz" on "users" ("foo", "bar")', $statements[0]); } + public function testAddingIndexOnline() + { + $blueprint = new Blueprint($this->getConnection(), 'users'); + $blueprint->index(['foo', 'bar'], 'baz')->online(); + $statements = $blueprint->toSql(); + + $this->assertCount(1, $statements); + $this->assertSame('create index "baz" on "users" ("foo", "bar") with (online = on)', $statements[0]); + } + public function testAddingSpatialIndex() { $blueprint = new Blueprint($this->getConnection(), 'geo'); diff --git a/tests/Database/TableGuesserTest.php b/tests/Database/TableGuesserTest.php index f983995e4dd5..d0a04c07d16f 100644 --- a/tests/Database/TableGuesserTest.php +++ b/tests/Database/TableGuesserTest.php @@ -28,6 +28,10 @@ public function testMigrationIsProperlyParsed() [$table, $create] = TableGuesser::guess('drop_status_column_from_users_table'); $this->assertSame('users', $table); $this->assertFalse($create); + + [$table, $create] = TableGuesser::guess('drop_users_table'); + $this->assertSame('users', $table); + $this->assertFalse($create); } public function testMigrationIsProperlyParsedWithoutTableSuffix() @@ -51,5 +55,9 @@ public function testMigrationIsProperlyParsedWithoutTableSuffix() [$table, $create] = TableGuesser::guess('drop_status_column_from_users'); $this->assertSame('users', $table); $this->assertFalse($create); + + [$table, $create] = TableGuesser::guess('drop_users'); + $this->assertSame('users', $table); + $this->assertFalse($create); } } diff --git a/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php b/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php index bfcf439d74ac..6d2308fb9d41 100644 --- a/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php +++ b/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php @@ -205,4 +205,27 @@ public function testGetRawIndex() $this->assertSame([], collect($indexes)->firstWhere('name', 'table_raw_index')['columns']); } + + public function testCreateIndexesOnline() + { + Schema::create('public.table', function (Blueprint $table) { + $table->id(); + $table->timestamps(); + $table->string('title', 200); + $table->text('body'); + + $table->unique('title')->online(); + $table->index(['created_at'])->online(); + $table->fullText(['body'])->online(); + $table->rawIndex("DATE_TRUNC('year'::text,created_at)", 'table_raw_index')->online(); + }); + + $indexes = Schema::getIndexes('public.table'); + $indexNames = collect($indexes)->pluck('name'); + + $this->assertContains('public_table_title_unique', $indexNames); + $this->assertContains('public_table_created_at_index', $indexNames); + $this->assertContains('public_table_body_fulltext', $indexNames); + $this->assertContains('table_raw_index', $indexNames); + } } diff --git a/tests/Integration/Database/SqlServer/DatabaseSqlServerSchemaBuilderTest.php b/tests/Integration/Database/SqlServer/DatabaseSqlServerSchemaBuilderTest.php index 501bed559c34..3a527ef2468f 100644 --- a/tests/Integration/Database/SqlServer/DatabaseSqlServerSchemaBuilderTest.php +++ b/tests/Integration/Database/SqlServer/DatabaseSqlServerSchemaBuilderTest.php @@ -73,4 +73,22 @@ public function testComputedColumnsListing() $userColumns = Schema::getColumns('users'); $this->assertNull($userColumns[1]['generation']); } + + public function testCreateIndexesOnline() + { + Schema::create('table', function (Blueprint $table) { + $table->id(); + $table->timestamps(); + $table->string('title', 200); + + $table->unique('title')->online(); + $table->index(['created_at'])->online(); + }); + + $indexes = Schema::getIndexes('table'); + $indexNames = collect($indexes)->pluck('name'); + + $this->assertContains('table_title_unique', $indexNames); + $this->assertContains('table_created_at_index', $indexNames); + } } diff --git a/tests/Integration/Http/ResourceTest.php b/tests/Integration/Http/ResourceTest.php index a0ec135b26b7..263b8f348995 100644 --- a/tests/Integration/Http/ResourceTest.php +++ b/tests/Integration/Http/ResourceTest.php @@ -901,7 +901,7 @@ public function testResourcesMayCustomizeJsonOptionsOnPaginatedResponse() ); $this->assertEquals( - '{"data":[{"id":5,"title":"Test Title","reading_time":3.0}],"links":{"first":"\/?page=1","last":"\/?page=1","prev":null,"next":null},"meta":{"current_page":1,"from":1,"last_page":1,"links":[{"url":null,"label":"« Previous","active":false},{"url":"\/?page=1","label":"1","active":true},{"url":null,"label":"Next »","active":false}],"path":"\/","per_page":15,"to":1,"total":10}}', + '{"data":[{"id":5,"title":"Test Title","reading_time":3.0}],"links":{"first":"\/?page=1","last":"\/?page=1","prev":null,"next":null},"meta":{"current_page":1,"from":1,"last_page":1,"links":[{"url":null,"label":"« Previous","page":null,"active":false},{"url":"\/?page=1","label":"1","page":1,"active":true},{"url":null,"label":"Next »","page":null,"active":false}],"path":"\/","per_page":15,"to":1,"total":10}}', $response->baseResponse->content() ); } diff --git a/tests/Queue/FailOnExceptionMiddlewareTest.php b/tests/Queue/FailOnExceptionMiddlewareTest.php index 7a0bd6c7b14b..a7904ab3e294 100644 --- a/tests/Queue/FailOnExceptionMiddlewareTest.php +++ b/tests/Queue/FailOnExceptionMiddlewareTest.php @@ -28,7 +28,7 @@ protected function setUp(): void /** * @return array, FailOnException, bool}> */ - public static function testMiddlewareDataProvider(): array + public static function middlewareDataProvider(): array { return [ 'exception is in list' => [ @@ -44,7 +44,7 @@ public static function testMiddlewareDataProvider(): array ]; } - #[DataProvider('testMiddlewareDataProvider')] + #[DataProvider('middlewareDataProvider')] public function test_middleware( string $thrown, FailOnException $middleware, diff --git a/tests/Validation/ValidationRequiredIfTest.php b/tests/Validation/ValidationRequiredIfTest.php index 619e7c72f566..69de61a95a73 100644 --- a/tests/Validation/ValidationRequiredIfTest.php +++ b/tests/Validation/ValidationRequiredIfTest.php @@ -77,5 +77,10 @@ public function testRequiredIfRuleValidation() $v = new Validator($trans, ['x' => 'foo'], ['x' => ['string', $rule]]); $this->assertTrue($v->passes()); + + $rule = new RequiredIf(null); + + $v = new Validator($trans, ['x' => 'foo'], ['x' => ['string', $rule]]); + $this->assertTrue($v->passes()); } } diff --git a/types/Support/Collection.php b/types/Support/Collection.php index 94da523a21b3..0d4d7834aa2c 100644 --- a/types/Support/Collection.php +++ b/types/Support/Collection.php @@ -691,8 +691,8 @@ function ($collection, $count) { assertType('Illuminate\Support\Collection', $collection->make(['string'])->concat(['string'])); assertType('Illuminate\Support\Collection', $collection->make([1])->concat(['string'])); -assertType('Illuminate\Support\Collection|int', $collection->make([1])->random(2)); -assertType('Illuminate\Support\Collection|string', $collection->make(['string'])->random()); +assertType('Illuminate\Support\Collection', $collection::make([1])->random(2)); +assertType('string', $collection::make(['string'])->random()); assertType('1', $collection ->reduce(function ($null, $user) { @@ -997,8 +997,10 @@ function ($collection, $count) { assertType('Illuminate\Support\Collection', $collection->forget(1)); assertType('Illuminate\Support\Collection', $collection->forget([1, 2])); -assertType('Illuminate\Support\Collection|User|null', $collection->pop(1)); -assertType('Illuminate\Support\Collection|string|null', $collection::make([ +assertType('User|null', $collection->pop()); +assertType('Illuminate\Support\Collection', $collection->pop(2)); + +assertType('Illuminate\Support\Collection', $collection::make([ 'string-key-1' => 'string-value-1', 'string-key-2' => 'string-value-2', ])->pop(2)); @@ -1020,8 +1022,8 @@ function ($collection, $count) { 'string-key-1' => 'string-value-1', ])->put('string-key-2', 'string-value-2')); -assertType('Illuminate\Support\Collection|User|null', $collection->shift(1)); -assertType('Illuminate\Support\Collection|string|null', $collection::make([ +assertType('User|null', $collection->shift()); +assertType('Illuminate\Support\Collection', $collection::make([ 'string-key-1' => 'string-value-1', 'string-key-2' => 'string-value-2', ])->shift(2));