diff --git a/config/concurrency.php b/config/concurrency.php index 1d66b701f82..cb8022b4669 100644 --- a/config/concurrency.php +++ b/config/concurrency.php @@ -15,6 +15,6 @@ | */ - 'driver' => env('CONCURRENCY_DRIVER', 'process'), + 'default' => env('CONCURRENCY_DRIVER', 'process'), ]; diff --git a/phpstan.types.neon.dist b/phpstan.types.neon.dist index be73ed150cf..ae92137be35 100644 --- a/phpstan.types.neon.dist +++ b/phpstan.types.neon.dist @@ -2,3 +2,5 @@ parameters: level: max paths: - types + ignoreErrors: + - identifier: argument.templateType diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index fdfde565b89..6aed4397da6 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -486,9 +486,11 @@ public function getOrPut($key, $value) /** * Group an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $groupBy + * @template TGroupKey of array-key + * + * @param (callable(TValue, TKey): TGroupKey)|array|string $groupBy * @param bool $preserveKeys - * @return static> + * @return static> */ public function groupBy($groupBy, $preserveKeys = false) { @@ -537,8 +539,10 @@ public function groupBy($groupBy, $preserveKeys = false) /** * Key an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $keyBy - * @return static + * @template TNewKey of array-key + * + * @param (callable(TValue, TKey): TNewKey)|array|string $keyBy + * @return static */ public function keyBy($keyBy) { diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index 9bb29c9727b..fcf9fe504ff 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -518,17 +518,21 @@ public function get($key, $default = null); /** * Group an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $groupBy + * @template TGroupKey of array-key + * + * @param (callable(TValue, TKey): TGroupKey)|array|string $groupBy * @param bool $preserveKeys - * @return static> + * @return static> */ public function groupBy($groupBy, $preserveKeys = false); /** * Key an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $keyBy - * @return static + * @template TNewKey of array-key + * + * @param (callable(TValue, TKey): TNewKey)|array|string $keyBy + * @return static */ public function keyBy($keyBy); diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index ab0534685fa..d00da44f1d9 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -544,9 +544,11 @@ public function get($key, $default = null) /** * Group an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $groupBy + * @template TGroupKey of array-key + * + * @param (callable(TValue, TKey): TGroupKey)|array|string $groupBy * @param bool $preserveKeys - * @return static> + * @return static> */ public function groupBy($groupBy, $preserveKeys = false) { @@ -556,8 +558,10 @@ public function groupBy($groupBy, $preserveKeys = false) /** * Key an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $keyBy - * @return static + * @template TNewKey of array-key + * + * @param (callable(TValue, TKey): TNewKey)|array|string $keyBy + * @return static */ public function keyBy($keyBy) { diff --git a/src/Illuminate/Concurrency/ConcurrencyManager.php b/src/Illuminate/Concurrency/ConcurrencyManager.php index 27646401bd2..f8c472a3769 100644 --- a/src/Illuminate/Concurrency/ConcurrencyManager.php +++ b/src/Illuminate/Concurrency/ConcurrencyManager.php @@ -71,7 +71,9 @@ public function createSyncDriver(array $config) */ public function getDefaultInstance() { - return $this->app['config']['concurrency.default'] ?? 'process'; + return $this->app['config']['concurrency.default'] + ?? $this->app['config']['concurrency.driver'] + ?? 'process'; } /** @@ -83,6 +85,7 @@ public function getDefaultInstance() public function setDefaultInstance($name) { $this->app['config']['concurrency.default'] = $name; + $this->app['config']['concurrency.driver'] = $name; } /** @@ -94,7 +97,7 @@ public function setDefaultInstance($name) public function getInstanceConfig($name) { return $this->app['config']->get( - 'concurrency.drivers.'.$name, ['driver' => $name], + 'concurrency.driver.'.$name, ['driver' => $name], ); } } diff --git a/src/Illuminate/Concurrency/ProcessDriver.php b/src/Illuminate/Concurrency/ProcessDriver.php index 22b16289073..8ba3345f900 100644 --- a/src/Illuminate/Concurrency/ProcessDriver.php +++ b/src/Illuminate/Concurrency/ProcessDriver.php @@ -3,6 +3,7 @@ namespace Illuminate\Concurrency; use Closure; +use Illuminate\Console\Application; use Illuminate\Contracts\Concurrency\Driver; use Illuminate\Foundation\Defer\DeferredCallback; use Illuminate\Process\Factory as ProcessFactory; @@ -10,8 +11,6 @@ use Illuminate\Support\Arr; use Laravel\SerializableClosure\SerializableClosure; -use function Illuminate\Support\php_binary; - class ProcessDriver implements Driver { /** @@ -27,18 +26,13 @@ public function __construct(protected ProcessFactory $processFactory) */ public function run(Closure|array $tasks): array { - $php = $this->phpBinary(); - $artisan = $this->artisanBinary(); + $command = Application::formatCommandString('invoke-serialized-closure'); - $results = $this->processFactory->pool(function (Pool $pool) use ($tasks, $php, $artisan) { + $results = $this->processFactory->pool(function (Pool $pool) use ($tasks, $command) { foreach (Arr::wrap($tasks) as $task) { $pool->path(base_path())->env([ 'LARAVEL_INVOKABLE_CLOSURE' => serialize(new SerializableClosure($task)), - ])->command([ - $php, - $artisan, - 'invoke-serialized-closure', - ]); + ])->command($command); } })->start()->wait(); @@ -60,35 +54,14 @@ public function run(Closure|array $tasks): array */ public function defer(Closure|array $tasks): DeferredCallback { - $php = $this->phpBinary(); - $artisan = $this->artisanBinary(); + $command = Application::formatCommandString('invoke-serialized-closure'); - return defer(function () use ($tasks, $php, $artisan) { + return defer(function () use ($tasks, $command) { foreach (Arr::wrap($tasks) as $task) { $this->processFactory->path(base_path())->env([ 'LARAVEL_INVOKABLE_CLOSURE' => serialize(new SerializableClosure($task)), - ])->run([ - $php, - $artisan, - 'invoke-serialized-closure 2>&1 &', - ]); + ])->run($command.' 2>&1 &'); } }); } - - /** - * Get the PHP binary. - */ - protected function phpBinary(): string - { - return php_binary(); - } - - /** - * Get the Artisan binary. - */ - protected function artisanBinary(): string - { - return defined('ARTISAN_BINARY') ? ARTISAN_BINARY : 'artisan'; - } } diff --git a/src/Illuminate/Database/Connection.php b/src/Illuminate/Database/Connection.php index 723b586ec25..a8d50dca1f2 100755 --- a/src/Illuminate/Database/Connection.php +++ b/src/Illuminate/Database/Connection.php @@ -672,11 +672,11 @@ public function withoutPretending(Closure $callback) $this->pretending = false; - $result = $callback(); - - $this->pretending = true; - - return $result; + try { + return $callback(); + } finally { + $this->pretending = true; + } } /** diff --git a/src/Illuminate/Database/DetectsLostConnections.php b/src/Illuminate/Database/DetectsLostConnections.php index dc987adb411..237ffaab9e9 100644 --- a/src/Illuminate/Database/DetectsLostConnections.php +++ b/src/Illuminate/Database/DetectsLostConnections.php @@ -52,7 +52,6 @@ protected function causedByLostConnection(Throwable $e) 'SSL: Connection timed out', 'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.', 'Temporary failure in name resolution', - 'SSL: Broken pipe', 'SQLSTATE[08S01]: Communication link failure', 'SQLSTATE[08006] [7] could not connect to server: Connection refused Is the server running on host', 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: No route to host', @@ -71,6 +70,7 @@ protected function causedByLostConnection(Throwable $e) 'SQLSTATE[HY000] [2002] Network is unreachable', 'SQLSTATE[HY000] [2002] The requested address is not valid in its context', 'SQLSTATE[HY000] [2002] A socket operation was attempted to an unreachable network', + 'SQLSTATE[HY000] [2002] Operation now in progress', 'SQLSTATE[HY000]: General error: 3989', 'went away', 'No such file or directory', diff --git a/src/Illuminate/Database/Schema/ForeignKeyDefinition.php b/src/Illuminate/Database/Schema/ForeignKeyDefinition.php index 6682da30c81..1ce0361b937 100644 --- a/src/Illuminate/Database/Schema/ForeignKeyDefinition.php +++ b/src/Illuminate/Database/Schema/ForeignKeyDefinition.php @@ -34,6 +34,16 @@ public function restrictOnUpdate() return $this->onUpdate('restrict'); } + /** + * Indicate that updates should set the foreign key value to null. + * + * @return $this + */ + public function nullOnUpdate() + { + return $this->onUpdate('set null'); + } + /** * Indicate that updates should have "no action". * diff --git a/src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php b/src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php index 55749481da5..4c072bc8f2b 100644 --- a/src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php +++ b/src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php @@ -5,7 +5,6 @@ use ArrayAccess; use Closure; use Countable; -use Illuminate\Support\Collection; class DeferredCallbackCollection implements ArrayAccess, Countable { @@ -39,7 +38,7 @@ public function invoke(): void /** * Invoke the deferred callbacks if the given truth test evaluates to true. * - * @param \Closure $when + * @param \Closure|null $when * @return void */ public function invokeWhen(?Closure $when = null): void @@ -115,7 +114,7 @@ public function offsetGet(mixed $offset): mixed } /** - * Set teh callback with the given key. + * Set the callback with the given key. * * @param mixed $offset * @param mixed $value diff --git a/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php b/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php index 93ca06e958b..36ffb7b3e48 100644 --- a/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php +++ b/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php @@ -2,6 +2,7 @@ namespace Illuminate\Http\Middleware; +use Illuminate\Http\Response; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Vite; @@ -17,7 +18,7 @@ class AddLinkHeadersForPreloadedAssets public function handle($request, $next) { return tap($next($request), function ($response) { - if (Vite::preloadedAssets() !== []) { + if ($response instanceof Response && Vite::preloadedAssets() !== []) { $response->header('Link', Collection::make(Vite::preloadedAssets()) ->map(fn ($attributes, $url) => "<{$url}>; ".implode('; ', $attributes)) ->join(', ')); diff --git a/src/Illuminate/Routing/Route.php b/src/Illuminate/Routing/Route.php index b114d22203d..2f44fa71139 100755 --- a/src/Illuminate/Routing/Route.php +++ b/src/Illuminate/Routing/Route.php @@ -1081,12 +1081,14 @@ public function middleware($middleware = null) /** * Specify that the "Authorize" / "can" middleware should be applied to the route with the given options. * - * @param string $ability + * @param \BackedEnum|string $ability * @param array|string $models * @return $this */ public function can($ability, $models = []) { + $ability = $ability instanceof BackedEnum ? $ability->value : $ability; + return empty($models) ? $this->middleware(['can:'.$ability]) : $this->middleware(['can:'.$ability.','.implode(',', Arr::wrap($models))]); diff --git a/src/Illuminate/Support/Facades/View.php b/src/Illuminate/Support/Facades/View.php index 10eaa645d54..90eb67bb66b 100755 --- a/src/Illuminate/Support/Facades/View.php +++ b/src/Illuminate/Support/Facades/View.php @@ -18,6 +18,7 @@ * @method static bool hasRenderedOnce(string $id) * @method static void markAsRenderedOnce(string $id) * @method static void addLocation(string $location) + * @method static void prependLocation(string $location) * @method static \Illuminate\View\Factory addNamespace(string $namespace, string|array $hints) * @method static \Illuminate\View\Factory prependNamespace(string $namespace, string|array $hints) * @method static \Illuminate\View\Factory replaceNamespace(string $namespace, string|array $hints) diff --git a/src/Illuminate/View/Factory.php b/src/Illuminate/View/Factory.php index bc6e59d55af..e5efe067e86 100755 --- a/src/Illuminate/View/Factory.php +++ b/src/Illuminate/View/Factory.php @@ -424,6 +424,17 @@ public function addLocation($location) $this->finder->addLocation($location); } + /** + * Prepend a location to the array of view locations. + * + * @param string $location + * @return void + */ + public function prependLocation($location) + { + $this->finder->prependLocation($location); + } + /** * Add a new namespace to the loader. * diff --git a/tests/Http/Middleware/VitePreloadingTest.php b/tests/Http/Middleware/VitePreloadingTest.php index 641280cc8d9..63b64b99519 100644 --- a/tests/Http/Middleware/VitePreloadingTest.php +++ b/tests/Http/Middleware/VitePreloadingTest.php @@ -9,6 +9,7 @@ use Illuminate\Http\Response; use Illuminate\Support\Facades\Facade; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Response as SymfonyResponse; class VitePreloadingTest extends TestCase { @@ -20,7 +21,7 @@ protected function tearDown(): void public function testItDoesNotSetLinkTagWhenNoTagsHaveBeenPreloaded() { - $app = new Container(); + $app = new Container; $app->instance(Vite::class, new class extends Vite { protected $preloadedAssets = []; @@ -36,7 +37,7 @@ public function testItDoesNotSetLinkTagWhenNoTagsHaveBeenPreloaded() public function testItAddsPreloadLinkHeader() { - $app = new Container(); + $app = new Container; $app->instance(Vite::class, new class extends Vite { protected $preloadedAssets = [ @@ -57,4 +58,25 @@ public function testItAddsPreloadLinkHeader() '; rel="modulepreload"; foo="bar"' ); } + + public function testItDoesNotAttachHeadersToNonIlluminateResponses() + { + $app = new Container; + $app->instance(Vite::class, new class extends Vite + { + protected $preloadedAssets = [ + 'https://laravel.com/app.js' => [ + 'rel="modulepreload"', + 'foo="bar"', + ], + ]; + }); + Facade::setFacadeApplication($app); + + $response = (new AddLinkHeadersForPreloadedAssets)->handle(new Request, function () { + return new SymfonyResponse('Hello Laravel'); + }); + + $this->assertNull($response->headers->get('Link')); + } } diff --git a/tests/Integration/Routing/AbilityBackedEnum.php b/tests/Integration/Routing/AbilityBackedEnum.php new file mode 100644 index 00000000000..129b0b30370 --- /dev/null +++ b/tests/Integration/Routing/AbilityBackedEnum.php @@ -0,0 +1,9 @@ + false); + $this->assertArrayHasKey('not-access-route', $gate->abilities()); + + $route = Route::get('/', function () { + return 'Hello World'; + })->can(AbilityBackedEnum::NotAccessRoute); + $this->assertEquals(['can:not-access-route'], $route->middleware()); + + $response = $this->get('/'); + $response->assertForbidden(); + } + + public function testSimpleRouteWithStringBackedEnumCanAbilityGuestAllowedThroughTheFramework() + { + $gate = Gate::define(AbilityBackedEnum::AccessRoute, fn (?User $user) => true); + $this->assertArrayHasKey('access-route', $gate->abilities()); + + $route = Route::get('/', function () { + return 'Hello World'; + })->can(AbilityBackedEnum::AccessRoute); + $this->assertEquals(['can:access-route'], $route->middleware()); + + $response = $this->get('/'); + $response->assertOk(); + $response->assertContent('Hello World'); + } +} diff --git a/types/Support/Collection.php b/types/Support/Collection.php index 290973923f1..8a0805ee3be 100644 --- a/types/Support/Collection.php +++ b/types/Support/Collection.php @@ -514,22 +514,23 @@ function ($collection, $count) { assertType('Illuminate\Support\Collection', $collection::make(['string'])->flip()); -assertType('Illuminate\Support\Collection<(int|string), Illuminate\Support\Collection<(int|string), User>>', $collection->groupBy('name')); -assertType('Illuminate\Support\Collection<(int|string), Illuminate\Support\Collection<(int|string), User>>', $collection->groupBy('name', true)); -assertType('Illuminate\Support\Collection<(int|string), Illuminate\Support\Collection<(int|string), User>>', $collection->groupBy(function ($user, $int) { - // assertType('User', $user); - // assertType('int', $int); +assertType('Illuminate\Support\Collection<(int|string), Illuminate\Support\Collection>', $collection->groupBy('name')); +assertType('Illuminate\Support\Collection<(int|string), Illuminate\Support\Collection>', $collection->groupBy('name', true)); +assertType('Illuminate\Support\Collection>', $collection->groupBy(function ($user, $int) { + assertType('User', $user); + assertType('int', $int); return 'foo'; })); -assertType('Illuminate\Support\Collection<(int|string), Illuminate\Support\Collection<(int|string), User>>', $collection->groupBy(function ($user) { + +assertType('Illuminate\Support\Collection>', $collection->keyBy(fn () => '')->groupBy(function ($user) { return 'foo'; -})); +}, preserveKeys: true)); assertType('Illuminate\Support\Collection<(int|string), User>', $collection->keyBy('name')); -assertType('Illuminate\Support\Collection<(int|string), User>', $collection->keyBy(function ($user, $int) { - // assertType('User', $user); - // assertType('int', $int); +assertType('Illuminate\Support\Collection', $collection->keyBy(function ($user, $int) { + assertType('User', $user); + assertType('int', $int); return 'foo'; })); diff --git a/types/Support/LazyCollection.php b/types/Support/LazyCollection.php index b3b8f73637b..51694fdf448 100644 --- a/types/Support/LazyCollection.php +++ b/types/Support/LazyCollection.php @@ -393,22 +393,22 @@ assertType('Illuminate\Support\LazyCollection', $collection::make(['string'])->flip()); -assertType('Illuminate\Support\LazyCollection<(int|string), Illuminate\Support\LazyCollection<(int|string), User>>', $collection->groupBy('name')); -assertType('Illuminate\Support\LazyCollection<(int|string), Illuminate\Support\LazyCollection<(int|string), User>>', $collection->groupBy('name', true)); -assertType('Illuminate\Support\LazyCollection<(int|string), Illuminate\Support\LazyCollection<(int|string), User>>', $collection->groupBy(function ($user, $int) { - // assertType('User', $user); - // assertType('int', $int); +assertType('Illuminate\Support\LazyCollection<(int|string), Illuminate\Support\LazyCollection>', $collection->groupBy('name')); +assertType('Illuminate\Support\LazyCollection<(int|string), Illuminate\Support\LazyCollection>', $collection->groupBy('name', true)); +assertType('Illuminate\Support\LazyCollection>', $collection->groupBy(function ($user, $int) { + assertType('User', $user); + assertType('int', $int); return 'foo'; })); -assertType('Illuminate\Support\LazyCollection<(int|string), Illuminate\Support\LazyCollection<(int|string), User>>', $collection->groupBy(function ($user) { +assertType('Illuminate\Support\LazyCollection>', $collection->keyBy(fn () => '')->groupBy(function ($user) { return 'foo'; -})); +}, true)); assertType('Illuminate\Support\LazyCollection<(int|string), User>', $collection->keyBy('name')); -assertType('Illuminate\Support\LazyCollection<(int|string), User>', $collection->keyBy(function ($user, $int) { - // assertType('User', $user); - // assertType('int', $int); +assertType('Illuminate\Support\LazyCollection', $collection->keyBy(function ($user, $int) { + assertType('User', $user); + assertType('int', $int); return 'foo'; }));