diff --git a/CHANGELOG.md b/CHANGELOG.md index b2cafe7f4574..dc2f134bf2f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.38.1...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.38.2...10.x) + +## [v10.38.2](https://github.com/laravel/framework/compare/v10.38.1...v10.38.2) - 2023-12-22 + +* [10.x] Add `conflict` for `doctrine/dbal:^4.0` to `illuminate/database` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/49456 +* [10.x] Simplify Arr::dot by [@bastien-phi](https://github.com/bastien-phi) in https://github.com/laravel/framework/pull/49461 +* [10.x] Illuminate\Filesystem\join_paths(): Argument #2 must be of type string, null given by [@tylernathanreed](https://github.com/tylernathanreed) in https://github.com/laravel/framework/pull/49467 +* [10.x] Allow deprecation logging in tests by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/49457 +* [10.x] Fix missing Validation rules not working with nested array by [@aabadawy](https://github.com/aabadawy) in https://github.com/laravel/framework/pull/49449 ## [v10.38.1](https://github.com/laravel/framework/compare/v10.38.0...v10.38.1) - 2023-12-20 diff --git a/composer.json b/composer.json index 5af97271ad45..dac49e5946c7 100644 --- a/composer.json +++ b/composer.json @@ -105,7 +105,7 @@ "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", "nyholm/psr7": "^1.2", - "orchestra/testbench-core": "^8.15.1", + "orchestra/testbench-core": "^8.18", "pda/pheanstalk": "^4.0", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^10.0.7", diff --git a/src/Illuminate/Console/Concerns/ConfiguresPrompts.php b/src/Illuminate/Console/Concerns/ConfiguresPrompts.php index 7bca27f45376..45d59495bf67 100644 --- a/src/Illuminate/Console/Concerns/ConfiguresPrompts.php +++ b/src/Illuminate/Console/Concerns/ConfiguresPrompts.php @@ -2,6 +2,7 @@ namespace Illuminate\Console\Concerns; +use Illuminate\Console\PromptValidationException; use Laravel\Prompts\ConfirmPrompt; use Laravel\Prompts\MultiSearchPrompt; use Laravel\Prompts\MultiSelectPrompt; @@ -132,7 +133,11 @@ protected function promptUntilValid($prompt, $required, $validate) if ($required && ($result === '' || $result === [] || $result === false)) { $this->components->error(is_string($required) ? $required : 'Required.'); - continue; + if ($this->laravel->runningUnitTests()) { + throw new PromptValidationException; + } else { + continue; + } } if ($validate) { @@ -141,7 +146,11 @@ protected function promptUntilValid($prompt, $required, $validate) if (is_string($error) && strlen($error) > 0) { $this->components->error($error); - continue; + if ($this->laravel->runningUnitTests()) { + throw new PromptValidationException; + } else { + continue; + } } } diff --git a/src/Illuminate/Console/PromptValidationException.php b/src/Illuminate/Console/PromptValidationException.php new file mode 100644 index 000000000000..218720967a0b --- /dev/null +++ b/src/Illuminate/Console/PromptValidationException.php @@ -0,0 +1,9 @@ +getConfig($name); + + if (is_null($config)) { + throw new InvalidArgumentException("Mailer [{$name}] is not defined."); + } + + // Now, we will check if the "driver" key exists and if it does we will set + // the transport configuration parameter in order to offer compatibility + // with any Laravel <= 6.x application style mail configuration files. + $transports[] = $this->app['config']['mail.driver'] + ? $this->createSymfonyTransport(array_merge($config, ['transport' => $name])) + : $this->createSymfonyTransport($config); + } + + return new RoundRobinTransport($transports); + } + /** * Create an instance of the Log Transport driver. * diff --git a/src/Illuminate/Queue/Queue.php b/src/Illuminate/Queue/Queue.php index 4f36b80db75b..1aa09ee30bdb 100755 --- a/src/Illuminate/Queue/Queue.php +++ b/src/Illuminate/Queue/Queue.php @@ -142,7 +142,7 @@ protected function createObjectPayload($job, $queue) 'uuid' => (string) Str::uuid(), 'displayName' => $this->getDisplayName($job), 'job' => 'Illuminate\Queue\CallQueuedHandler@call', - 'maxTries' => $job->tries ?? null, + 'maxTries' => $this->getJobTries($job) ?? null, 'maxExceptions' => $job->maxExceptions ?? null, 'failOnTimeout' => $job->failOnTimeout ?? false, 'backoff' => $this->getJobBackoff($job), @@ -178,6 +178,27 @@ protected function getDisplayName($job) ? $job->displayName() : get_class($job); } + /** + * Get the maximum number of attempts for an object-based queue handler. + * + * @param mixed $job + * @return mixed + */ + public function getJobTries($job) + { + if (! method_exists($job, 'tries') && ! isset($job->tries)) { + return; + } + + if (isset($job->tries)) { + return $job->tries; + } + + if (method_exists($job, 'tries') && ! is_null($job->tries())) { + return $job->tries(); + } + } + /** * Get the backoff for an object-based queue handler. * diff --git a/src/Illuminate/Support/Facades/Queue.php b/src/Illuminate/Support/Facades/Queue.php index 34663eb15ff6..2646f09d6658 100755 --- a/src/Illuminate/Support/Facades/Queue.php +++ b/src/Illuminate/Support/Facades/Queue.php @@ -31,6 +31,7 @@ * @method static \Illuminate\Contracts\Queue\Job|null pop(string|null $queue = null) * @method static string getConnectionName() * @method static \Illuminate\Contracts\Queue\Queue setConnectionName(string $name) + * @method static mixed getJobTries(mixed $job) * @method static mixed getJobBackoff(mixed $job) * @method static mixed getJobExpiration(mixed $job) * @method static void createPayloadUsing(callable|null $callback) diff --git a/src/Illuminate/Support/Testing/Fakes/QueueFake.php b/src/Illuminate/Support/Testing/Fakes/QueueFake.php index 032136089174..4117d5619754 100644 --- a/src/Illuminate/Support/Testing/Fakes/QueueFake.php +++ b/src/Illuminate/Support/Testing/Fakes/QueueFake.php @@ -359,7 +359,7 @@ public function push($job, $data = '', $queue = null) } $this->jobs[is_object($job) ? get_class($job) : $job][] = [ - 'job' => $this->serializeAndRestore ? $this->serializeAndRestoreJob($job) : $job, + 'job' => $this->serializeAndRestore ? $this->serializeAndRestoreJob($job) : $job, 'queue' => $queue, 'data' => $data, ]; diff --git a/src/Illuminate/Testing/PendingCommand.php b/src/Illuminate/Testing/PendingCommand.php index 56d5fad78208..d2f75737e510 100644 --- a/src/Illuminate/Testing/PendingCommand.php +++ b/src/Illuminate/Testing/PendingCommand.php @@ -3,6 +3,7 @@ namespace Illuminate\Testing; use Illuminate\Console\OutputStyle; +use Illuminate\Console\PromptValidationException; use Illuminate\Contracts\Console\Kernel; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Support\Arrayable; @@ -300,6 +301,8 @@ public function run() } throw $e; + } catch (PromptValidationException) { + $exitCode = Command::FAILURE; } if ($this->expectedExitCode !== null) { diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index 312c0b71e34c..b4335fe90059 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -1430,11 +1430,11 @@ public function validateMacAddress($attribute, $value) */ public function validateJson($attribute, $value) { - if (is_array($value)) { + if (is_array($value) || is_null($value)) { return false; } - if (! is_scalar($value) && ! is_null($value) && ! method_exists($value, '__toString')) { + if (! is_scalar($value) && ! method_exists($value, '__toString')) { return false; } diff --git a/src/Illuminate/View/Compilers/Concerns/CompilesUseStatements.php b/src/Illuminate/View/Compilers/Concerns/CompilesUseStatements.php index 8d74a77046d6..8218c9fdf2c6 100644 --- a/src/Illuminate/View/Compilers/Concerns/CompilesUseStatements.php +++ b/src/Illuminate/View/Compilers/Concerns/CompilesUseStatements.php @@ -14,7 +14,7 @@ protected function compileUse($expression) { $segments = explode(',', preg_replace("/[\(\)]/", '', $expression)); - $use = trim($segments[0], " '\""); + $use = ltrim(trim($segments[0], " '\""), '\\'); $as = isset($segments[1]) ? ' as '.trim($segments[1], " '\"") : ''; return ""; diff --git a/tests/Foundation/FoundationViteTest.php b/tests/Foundation/FoundationViteTest.php index dc7defb288d3..4e3efe78bd4a 100644 --- a/tests/Foundation/FoundationViteTest.php +++ b/tests/Foundation/FoundationViteTest.php @@ -1137,21 +1137,21 @@ public function testItOnlyOutputsUniquePreloadTags() { $buildDir = Str::random(); $this->makeViteManifest([ - 'resources/js/app.css' => [ - 'file' => 'assets/app-versioned.css', - 'src' => 'resources/js/app.css', + 'resources/js/app.css' => [ + 'file' => 'assets/app-versioned.css', + 'src' => 'resources/js/app.css', ], - 'resources/js/Pages/Welcome.vue' => [ - 'file' => 'assets/Welcome-versioned.js', - 'src' => 'resources/js/Pages/Welcome.vue', - 'imports' => [ + 'resources/js/Pages/Welcome.vue' => [ + 'file' => 'assets/Welcome-versioned.js', + 'src' => 'resources/js/Pages/Welcome.vue', + 'imports' => [ 'resources/js/app.js', ], ], - 'resources/js/app.js' => [ - 'file' => 'assets/app-versioned.js', - 'src' => 'resources/js/app.js', - 'css' => [ + 'resources/js/app.js' => [ + 'file' => 'assets/app-versioned.js', + 'src' => 'resources/js/app.js', + 'css' => [ 'assets/app-versioned.css', ], ], diff --git a/tests/Integration/Console/PromptsValidationTest.php b/tests/Integration/Console/PromptsValidationTest.php new file mode 100644 index 000000000000..de45ef16b669 --- /dev/null +++ b/tests/Integration/Console/PromptsValidationTest.php @@ -0,0 +1,35 @@ +registerCommand(new DummyPromptsValidationCommand()); + } + + public function testValidationForPrompts() + { + $this + ->artisan(DummyPromptsValidationCommand::class) + ->expectsQuestion('Test', 'bar') + ->expectsOutputToContain('error!'); + } +} + +class DummyPromptsValidationCommand extends Command +{ + protected $signature = 'prompts-validation-test'; + + public function handle() + { + text('Test', validate: fn ($value) => $value == 'foo' ? '' : 'error!'); + } +} diff --git a/tests/Integration/Mail/MailRoundRobinTransportTest.php b/tests/Integration/Mail/MailRoundRobinTransportTest.php new file mode 100644 index 000000000000..cb1bd3c05222 --- /dev/null +++ b/tests/Integration/Mail/MailRoundRobinTransportTest.php @@ -0,0 +1,27 @@ + 'roundrobin', 'mailers' => ['sendmail', 'array']])] + public function testGetRoundRobinTransportWithConfiguredTransports() + { + $transport = app('mailer')->getSymfonyTransport(); + $this->assertInstanceOf(RoundRobinTransport::class, $transport); + } + + #[WithConfig('mail.driver', 'roundrobin')] + #[WithConfig('mail.mailers', ['sendmail', 'array'])] + #[WithConfig('mail.sendmail', '/usr/sbin/sendmail -bs')] + public function testGetRoundRobinTransportWithLaravel6StyleMailConfiguration() + { + $transport = app('mailer')->getSymfonyTransport(); + $this->assertInstanceOf(RoundRobinTransport::class, $transport); + } +} diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index a539eb4b2b02..3ff9851fab4c 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -118,7 +118,7 @@ public function testDot() $this->assertSame([ 'user.name' => 'Taylor', 'user.age' => 25, - 'user.languages.0' =>'PHP', + 'user.languages.0' => 'PHP', 'user.languages.1' => 'C#', ], $array); diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index f86123de3a70..7c9d7b31d3a2 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -19,6 +19,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Carbon; use Illuminate\Support\Exceptions\MathException; +use Illuminate\Support\Stringable; use Illuminate\Translation\ArrayLoader; use Illuminate\Translation\Translator; use Illuminate\Validation\DatabasePresenceVerifierInterface; @@ -1928,31 +1929,31 @@ public function testValidateInArray() public function testValidateHexColor() { $trans = $this->getIlluminateArrayTranslator(); - $v = new Validator($trans, ['color'=> '#FFF'], ['color'=>'hex_color']); + $v = new Validator($trans, ['color' => '#FFF'], ['color' => 'hex_color']); $this->assertTrue($v->passes()); - $v = new Validator($trans, ['color'=> '#FFFF'], ['color'=>'hex_color']); + $v = new Validator($trans, ['color' => '#FFFF'], ['color' => 'hex_color']); $this->assertTrue($v->passes()); - $v = new Validator($trans, ['color'=> '#FFFFFF'], ['color'=>'hex_color']); + $v = new Validator($trans, ['color' => '#FFFFFF'], ['color' => 'hex_color']); $this->assertTrue($v->passes()); - $v = new Validator($trans, ['color'=> '#FF000080'], ['color'=>'hex_color']); + $v = new Validator($trans, ['color' => '#FF000080'], ['color' => 'hex_color']); $this->assertTrue($v->passes()); - $v = new Validator($trans, ['color'=> '#FF000080'], ['color'=>'hex_color']); + $v = new Validator($trans, ['color' => '#FF000080'], ['color' => 'hex_color']); $this->assertTrue($v->passes()); - $v = new Validator($trans, ['color'=> '#00FF0080'], ['color'=>'hex_color']); + $v = new Validator($trans, ['color' => '#00FF0080'], ['color' => 'hex_color']); $this->assertTrue($v->passes()); - $v = new Validator($trans, ['color'=> '#GGG'], ['color'=>'hex_color']); + $v = new Validator($trans, ['color' => '#GGG'], ['color' => 'hex_color']); $this->assertFalse($v->passes()); - $v = new Validator($trans, ['color'=> '#GGGG'], ['color'=>'hex_color']); + $v = new Validator($trans, ['color' => '#GGGG'], ['color' => 'hex_color']); $this->assertFalse($v->passes()); - $v = new Validator($trans, ['color'=> '#123AB'], ['color'=>'hex_color']); + $v = new Validator($trans, ['color' => '#123AB'], ['color' => 'hex_color']); $this->assertFalse($v->passes()); - $v = new Validator($trans, ['color'=> '#GGGGGG'], ['color'=>'hex_color']); + $v = new Validator($trans, ['color' => '#GGGGGG'], ['color' => 'hex_color']); $this->assertFalse($v->passes()); - $v = new Validator($trans, ['color'=> '#GGGGGGG'], ['color'=>'hex_color']); + $v = new Validator($trans, ['color' => '#GGGGGGG'], ['color' => 'hex_color']); $this->assertFalse($v->passes()); - $v = new Validator($trans, ['color'=> '#FFGG00FF'], ['color'=>'hex_color']); + $v = new Validator($trans, ['color' => '#FFGG00FF'], ['color' => 'hex_color']); $this->assertFalse($v->passes()); - $v = new Validator($trans, ['color'=> '#00FF008X'], ['color'=>'hex_color']); + $v = new Validator($trans, ['color' => '#00FF008X'], ['color' => 'hex_color']); $this->assertFalse($v->passes()); } @@ -2817,6 +2818,14 @@ public function testValidateJson() $trans = $this->getIlluminateArrayTranslator(); $v = new Validator($trans, ['foo' => ['array']], ['foo' => 'json']); $this->assertFalse($v->passes()); + + $trans = $this->getIlluminateArrayTranslator(); + $v = new Validator($trans, ['foo' => null], ['foo' => 'json']); + $this->assertFalse($v->passes()); + + $trans = $this->getIlluminateArrayTranslator(); + $v = new Validator($trans, ['foo' => new Stringable('[]')], ['foo' => 'json']); + $this->assertTrue($v->passes()); } public function testValidateBoolean() @@ -8653,10 +8662,10 @@ public function testExcludeBeforeADependentRule() $this->getIlluminateArrayTranslator(), [ 'profile_id' => null, - 'type' => 'denied', + 'type' => 'denied', ], [ - 'type' => ['required', 'string', 'exclude'], + 'type' => ['required', 'string', 'exclude'], 'profile_id' => ['nullable', 'required_if:type,profile', 'integer'], ], ); @@ -8668,10 +8677,10 @@ public function testExcludeBeforeADependentRule() $this->getIlluminateArrayTranslator(), [ 'profile_id' => null, - 'type' => 'profile', + 'type' => 'profile', ], [ - 'type' => ['required', 'string', 'exclude'], + 'type' => ['required', 'string', 'exclude'], 'profile_id' => ['nullable', 'required_if:type,profile', 'integer'], ], ); diff --git a/tests/View/Blade/BladeUseTest.php b/tests/View/Blade/BladeUseTest.php index bc1c8a708c2d..8e72c321540d 100644 --- a/tests/View/Blade/BladeUseTest.php +++ b/tests/View/Blade/BladeUseTest.php @@ -17,4 +17,18 @@ public function testUseStatementsWithoutAsAreCompiled() $expected = "Foo bar"; $this->assertEquals($expected, $this->compiler->compileString($string)); } + + public function testUseStatementsWithBackslashAtBeginningAreCompiled() + { + $string = "Foo @use('\SomeNamespace\SomeClass') bar"; + $expected = "Foo bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } + + public function testUseStatementsWithBackslashAtBeginningAndAliasedAreCompiled() + { + $string = "Foo @use('\SomeNamespace\SomeClass', 'Foo') bar"; + $expected = "Foo bar"; + $this->assertEquals($expected, $this->compiler->compileString($string)); + } }