05. Mengedit Chirp
Mari kita tambahkan fitur yang tidak ada di platform mikroblog bertema burung populer lainnya — kemampuan untuk mengedit Chirp!
Perutean (Routing)
Pertama, kita akan memperbarui file rute kita untuk mengaktifkan rute chirps.update pada resource controller kita:
<?php
use App\Http\Controllers\ChirpController; use App\Http\Controllers\ProfileController; use Illuminate\Foundation\Application; use Illuminate\Support\Facades\Route; use Inertia\Inertia; Route::get('/', function () { return Inertia::render('Welcome', [ 'canLogin' => Route::has('login'), 'canRegister' => Route::has('register'), 'laravelVersion' => Application::VERSION, 'phpVersion' => PHP_VERSION, ]); }); Route::get('/dashboard', function () { return Inertia::render('Dashboard'); })->middleware(['auth', 'verified'])->name('dashboard'); Route::middleware('auth')->group(function () { Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); }); Route::resource('chirps', ChirpController::class)- ->only(['index', 'store'])+ ->only(['index', 'store', 'update']) ->middleware(['auth', 'verified']);
require __DIR__.'/auth.php';
Tabel rute untuk controller ini sekarang terlihat seperti berikut:
| Verb | URI | Action | Route Name |
|---|---|---|---|
| GET | /chirps |
index | chirps.index |
| POST | /chirps |
store | chirps.store |
| PUT/PATCH | /chirps/{chirp} |
update | chirps.update |
Memperbarui komponen
Selanjutnya, mari perbarui komponen Chirp agar memiliki formulir edit untuk Chirp yang sudah ada.
Kita akan menggunakan komponen Dropdown bawaan Breeze, yang hanya akan kita tampilkan kepada penulis Chirp. Kita juga akan menampilkan indikasi jika sebuah Chirp telah diedit dengan membandingkan tanggal created_at milik Chirp dengan tanggal updated_at:
<script setup>+import Dropdown from '@/Components/Dropdown.vue';+import InputError from '@/Components/InputError.vue';+import PrimaryButton from '@/Components/PrimaryButton.vue'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime';+import { useForm } from '@inertiajs/vue3';+import { ref } from 'vue'; dayjs.extend(relativeTime); -defineProps(['chirp']);+const props = defineProps(['chirp']);+ +const form = useForm({+ message: props.chirp.message,+});+ +const editing = ref(false); </script> <template> <div class="p-6 flex space-x-2"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600 -scale-x-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" /> </svg> <div class="flex-1"> <div class="flex justify-between items-center"> <div> <span class="text-gray-800">{{ chirp.user.name }}</span> <small class="ml-2 text-sm text-gray-600">{{ dayjs(chirp.created_at).fromNow() }}</small>+ <small v-if="chirp.created_at !== chirp.updated_at" class="text-sm text-gray-600"> · edited</small> </div>+ <Dropdown v-if="chirp.user.id === $page.props.auth.user.id">+ <template #trigger>+ <button>+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor">+ <path d="M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z" />+ </svg>+ </button>+ </template>+ <template #content>+ <button class="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:bg-gray-100 transition duration-150 ease-in-out" @click="editing = true">+ Edit+ </button>+ </template>+ </Dropdown> </div>- <p class="mt-4 text-lg text-gray-900">{{ chirp.message }}</p>+ <form v-if="editing" @submit.prevent="form.put(route('chirps.update', chirp.id), { onSuccess: () => editing = false })">+ <textarea v-model="form.message" class="mt-4 w-full text-gray-900 border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"></textarea>+ <InputError :message="form.errors.message" class="mt-2" />+ <div class="space-x-2">+ <PrimaryButton class="mt-4">Save</PrimaryButton>+ <button class="mt-4" @click="editing = false; form.reset(); form.clearErrors()">Cancel</button>+ </div>+ </form>+ <p v-else class="mt-4 text-lg text-gray-900">{{ chirp.message }}</p> </div> </div> </template>
-import React from 'react';+import React, { useState } from 'react';+import Dropdown from '@/Components/Dropdown';+import InputError from '@/Components/InputError';+import PrimaryButton from '@/Components/PrimaryButton'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime';+import { useForm, usePage } from '@inertiajs/react'; dayjs.extend(relativeTime); export default function Chirp({ chirp }) {+ const { auth } = usePage().props;+ + const [editing, setEditing] = useState(false);+ + const { data, setData, patch, clearErrors, reset, errors } = useForm({+ message: chirp.message,+ });+ + const submit = (e) => {+ e.preventDefault();+ patch(route('chirps.update', chirp.id), { onSuccess: () => setEditing(false) });+ }; return ( <div className="p-6 flex space-x-2"> <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-gray-600 -scale-x-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"> <path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" /> </svg> <div className="flex-1"> <div className="flex justify-between items-center"> <div> <span className="text-gray-800">{chirp.user.name}</span> <small className="ml-2 text-sm text-gray-600">{dayjs(chirp.created_at).fromNow()}</small>+ { chirp.created_at !== chirp.updated_at && <small className="text-sm text-gray-600"> · edited</small>} </div>+ {chirp.user.id === auth.user.id &&+ <Dropdown>+ <Dropdown.Trigger>+ <button>+ <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor">+ <path d="M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z" />+ </svg>+ </button>+ </Dropdown.Trigger>+ <Dropdown.Content>+ <button className="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:bg-gray-100 transition duration-150 ease-in-out" onClick={() => setEditing(true)}>+ Edit+ </button>+ </Dropdown.Content>+ </Dropdown>+ } </div>- <p className="mt-4 text-lg text-gray-900">{chirp.message}</p>+ {editing+ ? <form onSubmit={submit}>+ <textarea value={data.message} onChange={e => setData('message', e.target.value)} className="mt-4 w-full text-gray-900 border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"></textarea>+ <InputError message={errors.message} className="mt-2" />+ <div className="space-x-2">+ <PrimaryButton className="mt-4">Save</PrimaryButton>+ <button className="mt-4" onClick={() => { setEditing(false); reset(); clearErrors(); }}>Cancel</button>+ </div>+ </form>+ : <p className="mt-4 text-lg text-gray-900">{chirp.message}</p>+ } </div> </div> ) }
Memperbarui controller
Kita sekarang dapat memperbarui metode update pada kelas ChirpController untuk memvalidasi request dan memperbarui database. Meskipun kita hanya menampilkan tombol edit kepada penulis Chirp, kita juga perlu melakukan otorisasi permintaan untuk memastikan bahwa memang penulisnya lah yang melakukan pembaruan:
<?php
namespace App\Http\Controllers; use App\Models\Chirp; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request;+use Illuminate\Support\Facades\Gate; use Inertia\Inertia; use Inertia\Response; class ChirpController extends Controller {
/** * Menampilkan daftar resource. */ public function index(): Response { return Inertia::render('Chirps/Index', [ 'chirps' => Chirp::with('user:id,name')->latest()->get(), ]); } /** * Menampilkan formulir untuk membuat resource baru. */ public function create() { // } /** * Menyimpan resource yang baru dibuat ke dalam penyimpanan. */ public function store(Request $request): RedirectResponse { $validated = $request->validate([ 'message' => 'required|string|max:255', ]); $request->user()->chirps()->create($validated); return redirect(route('chirps.index')); } /** * Menampilkan resource yang dipilih/spesifik. */ public function show(Chirp $chirp) { // } /** * Menampilkan formulir untuk mengedit resource yang dipilih/spesifik. */ public function edit(Chirp $chirp) { // } /** * Memperbarui resource yang dipilih/spesifik di dalam penyimpanan. */- public function update(Request $request, Chirp $chirp)+ public function update(Request $request, Chirp $chirp): RedirectResponse {- //+ Gate::authorize('update', $chirp);+ + $validated = $request->validate([+ 'message' => 'required|string|max:255',+ ]);+ + $chirp->update($validated);+ + return redirect(route('chirps.index')); }
/** * Menghapus resource yang dipilih/spesifik dari penyimpanan. */ public function destroy(Chirp $chirp) { // } }
Anda mungkin menyadari bahwa aturan validasi diduplikasi pada metode store. Anda bisa mempertimbangkan untuk mengekstraknya menggunakan Validasi Request Formulir milik Laravel, yang memudahkan penggunaan kembali aturan validasi dan menjaga controller Anda tetap ringkas.
Otorisasi
Secara default, metode authorize akan mencegah siapa pun untuk memperbarui Chirp tersebut. Kita dapat menentukan siapa yang diizinkan untuk memperbaruinya dengan membuat Model Policy menggunakan perintah berikut:
php artisan make:policy ChirpPolicy --model=Chirp
Perintah ini akan membuat kelas policy di app/Policies/ChirpPolicy.php yang dapat kita perbarui untuk menentukan bahwa hanya penulisnya yang berwenang untuk mengedit Chirp:
<?php
namespace App\Policies; use App\Models\Chirp; use App\Models\User; use Illuminate\Auth\Access\HandlesAuthorization; class ChirpPolicy {
use HandlesAuthorization; /** * Tentukan apakah pengguna dapat melihat model apa saja. */ public function viewAny(User $user): bool { // } /** * Tentukan apakah pengguna dapat melihat model yang ditentukan. */ public function view(User $user, Chirp $chirp): bool { // } /** * Tentukan apakah pengguna dapat membuat model. */ public function create(User $user): bool { // } /** * Menentukan apakah pengguna dapat memperbarui model. */ public function update(User $user, Chirp $chirp): bool {- //+ return $chirp->user()->is($user); }
/** * Menentukan apakah pengguna dapat menghapus model. */ public function delete(User $user, Chirp $chirp): bool { // } /** * Menentukan apakah pengguna dapat merestorasi model. */ public function restore(User $user, Chirp $chirp): bool { // } /** * Menentukan apakah pengguna dapat menghapus model secara permanen. */ public function forceDelete(User $user, Chirp $chirp): bool { // } }
Coba sekarang
Saatnya menguji! Silakan edit beberapa Chirp menggunakan menu dropdown. Jika Anda mendaftarkan akun pengguna lain, Anda akan melihat bahwa hanya penulis dari sebuah Chirp yang dapat mengeditnya.