I'm using the following resource using shallow nesting
Route::resource('organizations.emaildomains', 'OrganizationEmailDomainController', ['except' => ['show']])->shallow();
Got the following table with two records which is the result of an index.blade.php
Let's say we want to edit tiagoperes.eu
to stackoverflow.com
. I'd go to the edit view
change the respective field and click the save button. This is the result
As you can see, the record isn't updated.
Checking the $request->all()
in the update() of the controller
and the form data in the network tab
and the data is posted to
http://localhost/app/public/emaildomains/7
which matches the URI in shallow nesting.
In edit.blade.php I have the following form to handle the updates
<form method="post" action="{{ route('emaildomains.update', ['emaildomain' => $email_domain->id]) }}" autocomplete="off">
@csrf
@method('put')
<h6 class="heading-small text-muted mb-4">{{ __('Email Domain information') }}</h6>
<div class="pl-lg-4">
<div class="form-group{{ $errors->has('organization_id') ? ' has-danger' : '' }}">
<label class="form-control-label" for="input-organization_id">{{ __('Organization') }}</label>
<select name="organization_id" id="input-organization" class="form-control{{ $errors->has('organization_id') ? ' is-invalid' : '' }}" placeholder="{{ __('Organization') }}" required>
@foreach ($organizations as $organization)
<option value="{{ $organization->id }}" {{ $organization->id == old('organization_id', $email_domain->organization->id) ? 'selected' : '' }}>{{ $organization->name }}</option>
@endforeach
</select>
@include('alerts.feedback', ['field' => 'organization_id'])
</div>
<div class="form-group{{ $errors->has('email_domain') ? ' has-danger' : '' }}">
<label class="form-control-label" for="input-email_domain">{{ __('Email Domain') }}</label>
<input type="text" name="email_domain" id="input-email_domain" class="form-control{{ $errors->has('email_domain') ? ' is-invalid' : '' }}" placeholder="{{ __('Email Domain') }}" value="{{ old('email_domain', $email_domain->email_domain) }}" required autofocus>
@include('alerts.feedback', ['field' => 'email_domain'])
</div>
<div class="text-center">
<button type="submit" class="btn btn-success mt-4">{{ __('Save') }}</button>
</div>
</div>
</form>
and here's both the edit and update methods in the OrganizationEmailDomainController
/**
* Show the form for editing the specified resource.
*
* @param \App\OrganizationEmailDomain $email_domain
* @return \Illuminate\Http\Response
*/
public function edit(Request $request, OrganizationEmailDomain $email_domain, Organization $model)
{
$path = $request->path();
$id = (int)explode('/', $path)[1];
$emailDomain = OrganizationEmailDomain::find($id);
return view('organizations.emaildomains.edit', ['email_domain' => $emailDomain->load('organization'), 'organizations' => $model::where('id', $emailDomain->organization_id)->get(['id', 'name'])]);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\OrganizationEmailDomain $email_domain
* @return \Illuminate\Http\Response
*/
public function update(Request $request, OrganizationEmailDomain $email_domain)
{
$email_domain->update($request->all());
$organization_id = (int)$request->all()['organization_id'];
return redirect()->route('organizations.emaildomains.index', ['organization' => $organization_id])->withStatus(__("Org's email domain successfully updated."));
}
and this is the model (notice I'm using a table with a different name than the expected by default - protected $table = 'email_domains';
)
class OrganizationEmailDomain extends Model
{
protected $fillable = [
'email_domain', 'organization_id'
];
protected $table = 'email_domains';
/**
* Get the organization
*
* @return \Organization
*/
public function organization()
{
return $this->belongsTo(Organization::class);
}
}
When injecting a model ID to a route or controller action, you will often query the database to retrieve the model that corresponds to that ID. Laravel route model binding provides a convenient way to automatically inject the model instances directly into your routes. For example, instead of injecting a OrganizationEmailDomain's ID, you can inject the entire OrganizationEmailDomain model instance that matches the given ID.
Laravel automatically resolves Eloquent models defined in routes or controller actions whose type-hinted variable names match a route segment name. For example:
use App\OrganizationEmailDomain;
Route::put('emaildomains/{emailDomain}', [OrganizationEmailDomainController::class, 'update']);
Then the following should be in your controller
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\OrganizationEmailDomain $emailDomain
* @return \Illuminate\Http\Response
*/
public function update(Request $request, OrganizationEmailDomain $emailDomain)
{
$emailDomain->update($request->all());
return redirect()->route('organizations.emaildomains.index', [
'organization' => $request->organization_id
])->withStatus(__("Org's email domain successfully updated."));
}
Note that if the variable name $emailDomain
is different from the route segment {emailDomain}
, then laravel will not be able to resolve the model. Therefore you will get an empty OrganizationEmailDomain model and it will not update any data. So make sure to put the same name defined in the route.
To check which is the correct name of the route run the command php artisan route:list
and you will see the route and the name of the segment.
In order to solve the problem, I ran
php artisan route:list
which showed
organizations/{organization}/emaildomains/{emaildomain} | organizations.emaildomains.update
So, changing the OrganizationEmailDomainController.php
to
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\OrganizationEmailDomainRequest $request
* @param \App\OrganizationEmailDomain $emaildomain
* @return \Illuminate\Http\Response
*/
public function update(OrganizationEmailDomainRequest $request, OrganizationEmailDomain $emaildomain)
{
$emaildomain->update($request->all());
return redirect()->route('organizations.emaildomains.index', ['organization' => $request->organization_id])->withStatus(__("Org's email domain successfully updated."));
}
was enough.
Notice that the only needed change was in the controller from $email_domain
to $emaildomain
yet also removed the unnecessary bit to get the organization_id and used instead the suggsted above through the $request.