Laravel — P45:控制器编辑/更新 (CMP)

时间:2023-02-12 22:59:16

在我们完整地结束这个项目之前,我们还有几篇文章要讲。之后我们将继续讨论更高级的主题。在本文中,我们将解决如何编辑记录。我们需要显示一个表单,其中已经为特定记录填充了所有表单字段,然后当我们单击更新时,我们实际上必须更新我们的记录。

我们的路由

路线已经创建。edit 和 update 路由将分别调用​​edit​​和​​update​​方法。该​​edit​​方法从表中提取记录并将其显示在表单中,然后该​​update​​方法更新记录。

Route::prefix('/personalcars')->group(function() {
Route::get('/{id}/edit', [PersonalCarController::class, 'edit']);
Route::put('/{id}', [PersonalCarController::class, 'update']);
});

编辑链接

为了进入我们的编辑页面,我们需要创建一个编辑按钮(或链接)。在我们的链接旁边​​index.blade.php​​添加一个链接。​​Edit​​​​Show​

<!-- ... -->

<th scope="col" class="px-6 py-3">
Edit
</th>

<!-- ... -->

<td class="px-6 py-4">
<a href="/personalcars/{{ $car->id }}/edit">
Edit Car
</a>
</td>

<!-- ... -->

我们文件的完整代码​​index.blade.php​​现在如下所示。

<x-layouts.app title="{{ $title }}">
<div class="flex bg-white mt-12">
<div class="items-center text-center lg:text-left px-8 md:px-12 lg:w-full">
<div class="relative overflow-x-auto">
@if (session('status'))
<div class="block bg-green-200 p-4">
{{ session('status') }}
</div>
@endif
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" class="px-6 py-3">
Year
</th>
<th scope="col" class="px-6 py-3">
Make
</th>
<th scope="col" class="px-6 py-3">
Model
</th>
<th scope="col" class="px-6 py-3">
Exterior Color
</th>
<th scope="col" class="px-6 py-3">
Current Value
</th>
<th scope="col" class="px-6 py-3">
View
</th>
<th scope="col" class="px-6 py-3">
Edit
</th>
</tr>
</thead>
<tbody>
@foreach($cars as $car)
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
{{ $car->year }}
</th>
<td class="px-6 py-4">
{{ $car->brand->name }}
</td>
<td class="px-6 py-4">
{{ $car->model->name }}
</td>
<td class="px-6 py-4">
{{ $car->exterior_color }}
</td>
<td class="px-6 py-4">
{{ $car->current_value }}
</td>
<td class="px-6 py-4">
<a href="/personalcars/{{ $car->id }}">
Show Car
</a>
</td>
<td class="px-6 py-4">
<a href="/personalcars/{{ $car->id }}/edit">
Edit Car
</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
<a href="/personalcars/create">
<button class="block bg-green-400 hover:bg-green-600 text-white uppercase text-lg mx-auto p-4 rounded" type="submit">Add New Car</button>
</a>
</x-layouts.app>

Laravel — P45:控制器编辑/更新 (CMP)

单击我们的链接​​Edit Car​​​将资源发送​​$id​​​到.​​edit​​​​PersonalCarController​

编辑方法

我们的​​edit​​方法需要返回一个预填的表格。这很简单。我们已经知道如何获取资源,​​id​​并且我们已经有了一个表单。我们可以而且应该使用​​create.blade.php​​表格。我们只需要复制它并将其存储在​​edit.blade.php​​.

要获取资源,我们将使用​​PersonalCar::find()​​我们用于我们的方法的同一段代码​​show​​。

<?php

namespace App\Http\Controllers;

use App\Models\Image;
use App\Models\PersonalCar;
use App\Models\PersonalCarBrand;
use App\Models\PersonalCarModel;
use Faker\Provider\Person;
use Illuminate\Http\Request;

class PersonalCarController extends Controller
{
// ...

/**
* Show the form for editing the specified resource.
*
* @param int $id
*/
public function edit($id)
{
$car = PersonalCar::with(['brand', 'model', 'images'])->find($id);

// send data to edit view
// the view injects the data into the form
// the view is returned to the user through the route
}

// ...
}

接下来,我们需要创建我们的​​edit.blade.php​​文件。请记住,我们只会复制我们的​​create.blade.php​​表格。

<x-layouts.app title="{{ $title }}">
<div class="flex bg-white mt-12">
<div class="flex justify-center items-center w-full">
<div class="w-1/2 bg-white rounded shadow-2xl p-8 m-4">
<h1 class="block w-full text-center text-gray-800 text-2xl font-bold mb-6">Add New Car</h1>
@if ($errors->any())
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
<form action="/personalcars/" method="post" enctype="multipart/form-data">
@csrf
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="year">Year</label>
<input class="border py-2 px-3 text-grey-800" type="number" name="year" min="1920" max="{{ date('Y') + 1 }}" placeholder="2003">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="make">Make</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="make" placeholder="Chevy">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="model">Model</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="model" placeholder="Corvette">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="password">Transmission</label>
<div class="flex items-center mb-4">
<input type="radio" name="is_manual" value="1" class="h-4 w-4 border-gray-300 focus:ring-2 focus:ring-blue-300" checked>
<label for="is-manual-1" class="text-sm font-medium text-gray-900 ml-2">
Manual
</label>
</div>
<div class="flex items-center mb-4">
<input type="radio" name="is_manual" value="0" class="h-4 w-4 border-gray-300 focus:ring-2 focus:ring-blue-300">
<label for="is-manual-2" class="text-sm font-medium text-gray-900 ml-2">
Automatic
</label>
</div>
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="exterior_color">Exterior Color</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="exterior_color" placeholder="Blue">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="purchase_amount">Purchase Amount (in USD)</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="purchase_amount" placeholder="9532.57">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="current_value">Current Value (in USD)</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="current_value" placeholder="95532.57">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="sales_amount">Sales Amount (in USD)</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="sales_amount" placeholder="0.00">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="date_purchased">Date Purchased</label>
<input class="border py-2 px-3 text-grey-800" type="date" name="date_purchased" >
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="date_sold">Date Sold</label>
<input class="border py-2 px-3 text-grey-800" type="date" name="date_sold" >
</div>
<div class="flex flex-col mb-4">
<div class="mb-8">
<label class="mb-2 font-bold text-lg text-gray-900 block" for="file">Select Image</label>
<input type="file" name="file" class="mb-2 text-lg text-gray-900" />
</div>
</div>

<button class="block bg-green-400 hover:bg-green-600 text-white uppercase text-lg mx-auto p-4 rounded" type="submit">Save Car</button>
</form>
</div>
</div>
</div>
</x-layouts.app>

在我们更新​​edit.blade.php​​视图之前,我们将传递​​$car​​给它并返回它。

public function edit($id)
{
$car = PersonalCar::with(['brand', 'model', 'images'])->find($id);

return view('personalcars/edit', [
'title' => "Edit " . $car->year . " " . $car->brand->name . " " . $car->model->name,
'car' => $car,
]);
}

编辑.blade.php

让我们对我们的​​edit.blade.php​​文件进行必要的调整。首先,我们需要更改标题。

<h1 class="block w-full text-center text-gray-800 text-2xl font-bold mb-6">
Edit Car
</h1>

接下来,我们的表单动作需要指向我们的​​personalcars/{{ $id }}​​路线。

<form action="/personalcars/{{ $car->id }}" method="post" enctype="multipart/form-data">

如果你还记得我们的路线,它实际上是在接受一个​​put​​请求。我们无法改变我们的​​method​​目标​​put​​,但我们可以发挥创造力。Blade 有一个​​@method​​指令允许我们传递其他类型的请求,比如​​put​​.

<form action="/personalcars/{{ $car->id }}" method="post" enctype="multipart/form-data">
@csrf
@method('put')

接下来,我们需要填充我们的字段。这是通过利用​​value​​我们每个字段中的属性来完成的。

例如,在我们的​​year​​输入字段中,我们将使用以下代码添加一个值。

<input 
class="border py-2 px-3 text-grey-800"
type="number"
name="year"

value="{{ $car->year }}"
min="1920"
max="{{ date('Y') + 1 }}"
placeholder="2003"
>

我们将对其余字段重复该过程。

<x-layouts.app title="{{ $title }}">
<div class="flex bg-white mt-12">
<div class="flex justify-center items-center w-full">
<div class="w-1/2 bg-white rounded shadow-2xl p-8 m-4">
<h1 class="block w-full text-center text-gray-800 text-2xl font-bold mb-6">Edit Car</h1>
@if ($errors->any())
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
<form action="/personalcars/{{ $car->id }}" method="post" enctype="multipart/form-data">
@csrf
@method('put')
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="year">Year</label>
<input class="border py-2 px-3 text-grey-800" type="number" name="year" value="{{ $car->year }}" min="1920" max="{{ date('Y') + 1 }}" placeholder="2003">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="make">Make</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="make" value="{{ $car->brand->name }}" placeholder="Chevy">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="model">Model</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="model" value="{{ $car->model->name }}" placeholder="Corvette">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="password">Transmission</label>
<div class="flex items-center mb-4">
<input type="radio" name="is_manual" value="1" class="h-4 w-4 border-gray-300 focus:ring-2 focus:ring-blue-300" {{ $car->is_manual == "Manual" ? "checked" : "" }}>
<label for="is-manual-1" class="text-sm font-medium text-gray-900 ml-2">
Manual
</label>
</div>
<div class="flex items-center mb-4">
<input type="radio" name="is_manual" value="0" class="h-4 w-4 border-gray-300 focus:ring-2 focus:ring-blue-300" {{ $car->is_manual == "Automatic" ? "checked" : "" }}>
<label for="is-manual-2" class="text-sm font-medium text-gray-900 ml-2">
Automatic
</label>
</div>
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="exterior_color">Exterior Color</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="exterior_color" value="{{ $car->exterior_color }}" placeholder="Blue">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="purchase_amount">Purchase Amount (in USD)</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="purchase_amount" value="{{ $car->purchase_amount }}"}} placeholder="9532.57">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="current_value">Current Value (in USD)</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="current_value" value="{{ $car->current_value }}" placeholder="95532.57">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="sales_amount">Sales Amount (in USD)</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="sales_amount" value="{{ $car->sales_amount == "N/A" ? 0 : $car->sales_amount }}" placeholder="0.00">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="date_purchased">Date Purchased</label>
<input class="border py-2 px-3 text-grey-800" type="date" name="date_purchased" value="{{ $car->date_purchased }}">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="date_sold">Date Sold</label>
<input class="border py-2 px-3 text-grey-800" type="date" name="date_sold" value="{{ $car->date_sold }}">
</div>

<div class="flex flex-col mb-4">
@foreach( $car->images as $image )
<div class="w-80 bg-white p-3">
<img class="h-52 w-full object-cover" src="{{ asset( 'storage/' . $image->url ) }}" alt="{{ $image->alt }}" />
</div>
@endforeach

<div class="mb-8">
<label class="mb-2 font-bold text-lg text-gray-900 block" for="file">Add Another Image</label>
<input type="file" name="file" class="mb-2 text-lg text-gray-900" />
</div>
</div>

<button class="block bg-green-400 hover:bg-green-600 text-white uppercase text-lg mx-auto p-4 rounded" type="submit">Update</button>
</form>
</div>
</div>
</div>
</x-layouts.app>

所有标准文本字段都遵循与刚才讨论的相同格式。我们的​​radio​​按钮检查我们的​​PersonalCar​​模型返回了什么。它将返回​​Automatic​​或​​Manual​​。根据哪一个匹配,相应的单选按钮被选中。

<input 

type="radio"
name="is_manual"
value="1"
class="h-4 w-4 border-gray-300 focus:ring-2 focus:ring-blue-300"
{{ $car->is_manual == "Manual" ? "checked" : "" }}
>

<input

type="radio"
name="is_manual"
value="0"
class="h-4 w-4 border-gray-300 focus:ring-2 focus:ring-blue-300"
{{ $car->is_manual == "Automatic" ? "checked" : "" }}
>

我们也可以通过我们的​​edit​​表格添加额外的图片。我们将在此处显示现有图像。

<div class="flex flex-col mb-4">
@foreach( $car->images as $image )
<div class="w-80 bg-white p-3">
<img class="h-52 w-full object-cover" src="{{ asset( 'storage/' . $image->url ) }}" alt="{{ $image->alt }}" />
</div>
@endforeach

<div class="mb-8">
<label class="mb-2 font-bold text-lg text-gray-900 block" for="file">Add Another Image</label>
<input type="file" name="file" class="mb-2 text-lg text-gray-900" />
</div>
</div>

我们的表单现在已完全填充。

Laravel — P45:控制器编辑/更新 (CMP)

更新方法

该​​update​​方法将与我们的​​store​​方法非常相似。唯一的区别是我们将首先访问​​find​​我们的资源并更新它,而不是插入新资源。

我们从相同的验证开始。

/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
*/
public function update(Request $request, $id)
{
$validated = $request->validate([
'year' => 'required|integer',
'make' => 'required|max:255',
'model' => 'required|max:255',
'is_manual' => 'required|boolean',
'exterior_color' => 'required|max:255',
'purchase_amount' => 'numeric',
'current_value' => 'numeric',
'sales_amount' => 'numeric|nullable',
'date_purchased' => 'required|date',
'date_sold' => 'date|nullable',
'file' => 'image|mimes:jpg,jpeg,png,gif|max:2048|nullable',
]);
}

如果您按原样运行代码,您会看到它已经验证失败。那是因为我们对​​purchase_amount​​,​​current_value​​和的格式化​​sales_amount​​。我们必须去掉非数字字符,比如 $。我们将在​​preg_replace​​这里使用一个简单的表达式。

preg_replace("#[^0-9.]#", "", $car->purchase_amount)
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="purchase_amount">Purchase Amount (in USD)</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="purchase_amount" value="{{ preg_replace("#[^0-9.]#", "", $car->purchase_amount) }}" placeholder="9532.57">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="current_value">Current Value (in USD)</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="current_value" value="{{ preg_replace("#[^0-9.]#", "", $car->current_value) }}" placeholder="95532.57">
</div>
<div class="flex flex-col mb-4">
<label class="mb-2 font-bold text-lg text-gray-900" for="sales_amount">Sales Amount (in USD)</label>
<input class="border py-2 px-3 text-grey-800" type="text" name="sales_amount" value="{{ $car->sales_amount == "N/A" ? 0 : preg_replace("#[^0-9.]#", "", $car->sales_amount) }}" placeholder="0.00">
</div>

现在这将允许我们通过验证。

接下来,我们需要找到资源。由于​​$id​​已传递给我们的​​update​​方法,我们可以简单地使用它。

public function update(Request $request, $id)
{
$validated = $request->validate([
'year' => 'required|integer',
'make' => 'required|max:255',
'model' => 'required|max:255',
'is_manual' => 'required|boolean',
'exterior_color' => 'required|max:255',
'purchase_amount' => 'numeric',
'current_value' => 'numeric',
'sales_amount' => 'numeric|nullable',
'date_purchased' => 'required|date',
'date_sold' => 'date|nullable',
'file' => 'image|mimes:jpg,jpeg,png,gif|max:2048|nullable',
]);

$car = PersonalCar::find($id);
}

最后,我们需要更新它。

我们将首先添加品牌和型号,或者如果它们存在则检索它们。接下来,我们将更新每个属性并将 和 关联​​brand​​到​​model​​它。最后,如果选择了图片(即 image != null),那么我们将上传图片和​​attach​​它。

应将用户重定向回​​edit​​带有成功消息的页面。

public function update(Request $request, $id)
{
$validated = $request->validate([
'year' => 'required|integer',
'make' => 'required|max:255',
'model' => 'required|max:255',
'is_manual' => 'required|boolean',
'exterior_color' => 'required|max:255',
'purchase_amount' => 'numeric',
'current_value' => 'numeric',
'sales_amount' => 'numeric|nullable',
'date_purchased' => 'required|date',
'date_sold' => 'date|nullable',
'file' => 'image|mimes:jpg,jpeg,png,gif|max:2048|nullable',
]);

$car = PersonalCar::find($id);

$brand = PersonalCarBrand::firstOrCreate([
'name' => $request->make,
], [
'slug' => str_replace(" ", "-", strtolower($request->make))
]);

$model = PersonalCarModel::firstOrCreate([
'name' => $request->model,
], [
'slug' => str_replace(" ", "-", strtolower($request->model))
]);

$car->year = $request->year;
$car->brand()->associate($brand);
$car->model()->associate($model);
$car->is_manual = $request->is_manual;
$car->exterior_color = $request->exterior_color;
$car->purchase_amount = $request->purchase_amount;
$car->current_value = $request->current_value;
$car->sales_amount = $request->sales_amount == 0 ? 0 : $request->sales_amount;
$car->date_purchased = $request->date_purchased;
$car->date_sold = $request->date_sold;

$car->save();

if ( $request->file('file') !== null ) {
$image_path = $request->file('file')->store('images', 'public');

$image = Image::create([
'url' => $image_path,
'alt' => $request->year . " " . $brand->name . " " . $model->name,
]);

$car->images()->attach($image);
}

return redirect()->to('/personalcars/' . $id . '/edit')->with('status', 'Your car has been updated.');
}

我们的成功消息需要能够显示在我们的​​edit.blade.php​​视图中。我们可以在错误检查下添加它。

<x-layouts.app title="{{ $title }}">
<div class="flex bg-white mt-12">
<div class="flex justify-center items-center w-full">
<div class="w-1/2 bg-white rounded shadow-2xl p-8 m-4">
<h1 class="block w-full text-center text-gray-800 text-2xl font-bold mb-6">Edit Car</h1>
@if ($errors->any())
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif

@if (session('status'))
<div class="block bg-green-200 p-4 mb-4">
{{ session('status') }}
</div>
@endif

<!-- ... -->

现在,如果我们进行修改,我们可以单击更新,我们的资源将被更新。

Laravel — P45:控制器编辑/更新 (CMP)

在下一篇文章中,我们将解决删除资源的问题,我们还将着眼于删除特定图像。