From ed23456791796196876364b5d20e9e8097d0ed06 Mon Sep 17 00:00:00 2001 From: Josh Salway Date: Thu, 19 Mar 2026 05:11:22 +1000 Subject: [PATCH 1/2] fix: make File::types() chainable on existing instances The types() method was declared as static, which meant calling it on an existing File instance (e.g. File::image()->max(1)->types(['jpg'])) would silently create a new instance, discarding all prior configuration like max(), min(), etc. This changes types() to work both as a static factory method and as a chainable instance method by leveraging __call and __callStatic magic methods. The underlying logic is moved to setTypes(). Fixes #59242 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/Illuminate/Validation/Rules/File.php | 53 ++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Validation/Rules/File.php b/src/Illuminate/Validation/Rules/File.php index b3968e035a37..d24e5a9c8139 100644 --- a/src/Illuminate/Validation/Rules/File.php +++ b/src/Illuminate/Validation/Rules/File.php @@ -14,7 +14,46 @@ class File implements Rule, DataAwareRule, ValidatorAwareRule { - use Conditionable, Macroable; + use Conditionable, Macroable { + Macroable::__call as macroCall; + Macroable::__callStatic as macroCallStatic; + } + + /** + * Handle dynamic calls to the object. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + if ($method === 'types') { + return $this->setTypes(...$parameters); + } + + return $this->macroCall($method, $parameters); + } + + /** + * Handle dynamic, static calls to the object. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public static function __callStatic($method, $parameters) + { + if ($method === 'types') { + return (new static())->setTypes(...$parameters); + } + + return static::macroCallStatic($method, $parameters); + } /** * The MIME types that the given file should match. This array may also contain file extensions. @@ -137,12 +176,18 @@ public static function image($allowSvg = false) /** * Limit the uploaded file to the given MIME types or file extensions. * + * This method can be called both statically (as a factory) and on an instance. + * When called statically, a new File instance is created. + * When called on an instance, the current instance is modified and returned. + * * @param string|array $mimetypes - * @return static + * @return $this */ - public static function types($mimetypes) + public function setTypes($mimetypes) { - return tap(new static(), fn ($file) => $file->allowedMimetypes = (array) $mimetypes); + $this->allowedMimetypes = (array) $mimetypes; + + return $this; } /** From a57ba0b898d5509f87b74acdd3856a083cf88323 Mon Sep 17 00:00:00 2001 From: Josh Salway Date: Thu, 19 Mar 2026 05:11:45 +1000 Subject: [PATCH 2/2] test: add test for File::types() chain preservation Verifies that calling types() on an existing File instance preserves prior configuration (e.g. max()) instead of creating a new instance. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/Validation/ValidationFileRuleTest.php | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/Validation/ValidationFileRuleTest.php b/tests/Validation/ValidationFileRuleTest.php index adbd7024c792..f289d5c547c0 100644 --- a/tests/Validation/ValidationFileRuleTest.php +++ b/tests/Validation/ValidationFileRuleTest.php @@ -135,6 +135,31 @@ public function testMixOfMimetypesAndMimes() ); } + public function testTypesPreservesChainConfiguration() + { + // When types() is called on an existing instance, it should preserve + // prior configuration like max() instead of creating a new instance. + $this->fails( + File::image()->max(1)->types(['jpg', 'jpeg']), + UploadedFile::fake()->image('photo.jpg', 800, 600), + ['validation.max.file'], + ); + + // When types() is called statically, it should still work as a factory. + $this->fails( + File::types('text/plain'), + UploadedFile::fake()->createWithContent('foo.png', file_get_contents(__DIR__.'/fixtures/image.png')), + ['validation.mimetypes'], + ); + + // When types() is called before max(), the max constraint should also work. + $this->fails( + File::default()->types(['jpg', 'jpeg'])->max(1), + UploadedFile::fake()->image('photo.jpg', 800, 600), + ['validation.max.file'], + ); + } + public function testSingleExtension() { $this->fails(