[13.x] Add mimetypes() instance method to File validation rule#59436
[13.x] Add mimetypes() instance method to File validation rule#59436sumaiazaman wants to merge 3 commits into
Conversation
File::types() is a static factory that creates a new instance, which silently discards any prior chain configuration (max, min, etc.) when called on an existing instance. The new mimetypes() instance method allows setting MIME types while preserving the fluent chain. Fixes laravel#59242
|
I predict, having two methods that essentially do the same thing, will confuse devs as to when to use which. Therefore, we could solve this the following way, even though, it's a little "magic" (see the alternative implementation #59324): - /**
- * Limit the uploaded file to the given MIME types or file extensions.
- *
- * @param string|array<int, string> $mimetypes
- * @return static
- */
- public static function types($mimetypes)
- {
- return tap(new static(), fn ($file) => $file->allowedMimetypes = (array) $mimetypes);
- }
+ /**
+ * Set the allowed MIME types or file extensions on an existing instance.
+ *
+ * @param string|array<int, string> $mimetypes
+ * @return $this
+ */
+ private function mimetypes($mimetypes)
+ {
+ $this->allowedMimetypes = (array) $mimetypes;
+
+ return $this;
+ }
+
+ /**
+ * @phpstan-return ($name is 'types' ? self : never)
+ *
+ * @throws \BadMethodCallException
+ */
+ public function __callStatic(string $name, mixed $arguments)
+ {
+ if ($name === 'types') {
+ return (new static())->types($arguments);
+ }
+
+ throw new BadMethodCallException(sprintf(
+ 'Method %s::%s does not exist.', static::class, $method
+ ));
+ }
+
+ /**
+ * @phpstan-return ($name is 'types' ? $this : never)
+ *
+ * @throws \BadMethodCallException
+ */
+ public function __call(string $name, mixed $arguments)
+ {
+ if ($name === 'types') {
+ return $this->types($arguments);
+ }
+
+ throw new BadMethodCallException(sprintf(
+ 'Method %s::%s does not exist.', static::class, $method
+ ));
+ } |
Good point about the two methods. Your __call/__callStatic approach would work, but it adds complexity — magic methods lose IDE autocomplete and click-through navigation, and need special PHPStan annotations. The reason I used a separate mimetypes() name is to keep it simple:
But I'm open to whatever approach the maintainers prefer. The key issue is that File::image()->max(1)->types(['jpg']) silently drops the max() constraint today, and developers won't know their validation is broken until a file that should be rejected gets through. |
Fixes #59242
Summary
File::types()is a static factory method that creates a new instance vianew static(). When called in a fluent chain on an existingFileorImageFileinstance, it silently discards all prior configuration (max(),min(),image(), etc.) because PHP resolves it as a static call that returns a brand new object.The Bug
The Fix
Added a
mimetypes()instance method that sets MIME types on the existing instance (returning$this), preserving the fluent chain. The statictypes()factory now delegates tomimetypes()internally, so existingFile::types(...)usage continues to work identically.Why This Doesn't Break Existing Features
File::types(...)as a static factory continues to work identically — it now calls(new static())->mimetypes(...)internallymimetypes()method is purely additiveChanges
src/Illuminate/Validation/Rules/File.php— Addedmimetypes()instance method, refactoredtypes()to use ittests/Validation/ValidationFileRuleTest.php— Added 2 tests: chain preservation and static factory backward compatTest Plan
testMimetypesInstanceMethodPreservesChain—max(1)constraint is preserved when chaining withmimetypes()testStaticTypesStillWorksAsFactory—File::types(...)continues to work as before