I'm sending some data from a form, one field is type file like this(simplified version) that is an avatar image:
<form id="userform" method="POST" action="/users/store" autocomplete="off" enctype="multipart/form-data">
<div >
<input id="avatar" type="file" name="avatar" accept="image/*">
<input id="name" type="text" name="name" >
<input id="email" type="text" name="email" >
<input id="password" type="password" name="password">
<input type="submit" id="submit" value="submit">
</div>
</form>
in the related controller I have something like this (simplified version) that checks if there is a file in the request and stores it in a dedicated folder with a unique name, then (and here is the problem) it will change the value of the avatar field in the request, so I can store it later in the db with the new unique name
// Update User
public function update(Request $request){
// Upload avatar
if( $request->hasFile('avatar') ) {
// Unique file name
$avatarName = $request['name'].'-'.uniqid().'-avatar.jpg';
// I save the file in the avatar folder with its new name
$request->file('avatar')->storeAs(
'avatars', $avatarName, 'public'
);
$request['avatar'] = $avatarName;
} else {
$request['avatar'] = config('options.default_avatar');
}
...
}
no matter how much I tried, I cannot change the name in the request, I tried in so many ways
$request['avatar'] = $avatarName;
$request->avatar = $avatarName;
$request->merge(['avatar'=>'$avatarName'])
if I try debugging
dd($request->all())
I always obtain this Illuminate\Http\UploadedFile, where the value of avatar doesn't changes
array:7 [
"action" => "update"
"id" => "50"
"_token" => "d0WSL69fEVEb71HDnXoirQJ2fsfe9hes3ZmCuyff"
"name" => "Lauras"
"password" => "$2y$10$g.4dGPWfEA2LjhZBcXFUiOTqwjM8PCHuooaaHnUWyPFoJFQihlDkC"
"password_confirmation" => "nt1access"
"avatar" => Illuminate\Http\UploadedFile {#313
-test: false
-originalName: "52F.jpg"
-mimeType: "image/jpeg"
-error: 0
#hashName: null
path: "/tmp"
filename: "phpWbVial"
basename: "phpWbVial"
pathname: "/tmp/phpWbVial"
extension: ""
realPath: "/tmp/phpWbVial"
aTime: 2022-07-13 22:08:15
mTime: 2022-07-13 22:08:15
cTime: 2022-07-13 22:08:15
inode: 406627
size: 200630
perms: 0100600
owner: 0
group: 0
type: "file"
writable: true
readable: true
executable: false
file: true
dir: false
link: false
}
]
I can change any value in the request before I update by simply doing
$request['field'] = $newFieldName;
but I cannot change the avatar field. something really strange is that if I do this
$request->avatar = $avatarName;
dd($request->avatar);
it will output the $avatarName that I want, but if I do
$request->avatar = $avatarName;
dd($request);
it will output again this Illuminate\Http\UploadedFile stuffs.
For now the only way to fix this, is to store the entire request in another variable and then manipulate the new variable, before I update the db, in this way
$data = $request->all();
this is not so bad ok, but I cannot understand why this is happening, is like everytime I call $request it will reset only the avatar field to the original value.
CodePudding user response:
I did some digging and I think I found out the issue.
In Symfony's Request
class, there's a property $request
which is where $_POST
is stored, and a property $files
which is where $_FILES
is stored.
Laravel's Request
class extends ArrayAccess
, and its OffsetSet
method looks like this:
public function offsetSet($offset, $value): void
{
$this->getInputSource()->set($offset, $value);
}
And the getInputSource
method looks like this:
protected function getInputSource()
{
if ($this->isJson()) {
return $this->json();
}
return in_array($this->getRealMethod(), ['GET', 'HEAD']) ? $this->query : $this->request;
}
In your case, your request isn't JSON, and it's not using the GET
or HEAD
methods, so the input source you're modifying is $this->request
.
This means that when you set $request['field'] = $newFieldName;
you're setting $this->request['field']
to $newFieldName
as you expect.
It also means that when you set $request['avatarName'] = $avatarName;
, you're also setting $this->request['avatarName']
to $avatarName
as you expect.
So what's the problem?
When you convert a Laravel Request
object to an array, it uses the toArray()
method on that class:
public function toArray(): array
{
return $this->all();
}
And in the all()
method (inherited from InteractsWithInput
) we find the issue:
public function all($keys = null)
{
$input = array_replace_recursive($this->input(), $this->allFiles());
...
$this->input()
is getting values from the result of getInputSource()
, which is to say Symfony's $request
property containing the values of $_POST
, and overwriting it with the values from $this->allFiles()
, which is getting values from $files
, which contains the values of $_FILES
.
So what does this all mean?
Long story short, this is a weird quirk of Laravel's implementation of ArrayAccess
on the Request
class. When you read/write to a Request
object with array notation, you're really just reading/writing the variables which were $_POST
ed (or the JSON-encoded body or $_GET
variables if your request was json-encoded or a GET
/HEAD
request).
If you want to continue down this path, you'll need to modify your $request->files
property. It's a Symfony FileBag
, which you can change with the set()
method:
$request->files->set('avatarName', /*Your modified UploadedFile object*/);
CodePudding user response:
The $request
is an object of class \Illuminate\Http\Request
. Its not a simple array. You can verify this if you do dd(get_class($request));
Setting it as:
$request['avatar'] = $avatarName;
$request->avatar = $avatarName;
Will not change set the value. So doing it this way:
$data = $request->all();
Will convert it to array and you can manipulate the $data
in any way you want