Pracując z Laravel i Inertia.js, szczególnie przy obsłudze przesyłania plików w operacjach aktualizacji, możesz napotkać na wspólny wzorzec, który na początku wydaje się nieintuicyjny: używanie żądań POST z _method: "PUT" zamiast faktycznych żądań PUT. Ten artykuł wyjaśnia, dlaczego to podejście jest konieczne i jak poprawnie je implementować.
Problem: Metody HTTP i przesyłanie plików
Zrozumienie metod HTTP
W RESTful API różne metody HTTP służą konkretnym celom:
GET- pobieranie danychPOST- tworzenie nowych zasobówPUT- aktualizacja istniejących zasobów (całkowite zastąpienie)PATCH- częściowe aktualizacjeDELETE- usuwanie zasobów
Logicznie rzecz biorąc, przy aktualizacji zasobu powinniśmy używać PUT lub PATCH. Jednak istnieje ograniczenie techniczne, które zmusza nas do zastosowania innego podejścia.
Ograniczenie przesyłania plików
Główny problem polega na tym, jak przeglądarki obsługują przesyłanie plików z różnymi metodami HTTP:
- Żądania POST mogą obsługiwać kodowanie
multipart/form-data, które jest wymagane do przesyłania plików - Żądania PUT w przeglądarkach nie obsługują właściwie
multipart/form-data - Większość przeglądarek natywnie obsługuje tylko metody
GETiPOSTw formularzach HTML
Rozwiązanie: Podszywanie się pod metodę (Method Spoofing)
Laravel oferuje eleganckie rozwiązanie zwane “method spoofing” wykorzystujące pole _method.
Jak działa Method Spoofing
// Zamiast tego (co nie zadziała z plikami):
router.put('/posts/1', {
title: 'Zaktualizowany tytuł',
content: 'Zaktualizowana treść',
image: fileObject // To nie zadziała prawidłowo
});
// Używamy tego:
router.post('/posts/1', {
_method: 'PUT',
title: 'Zaktualizowany tytuł',
content: 'Zaktualizowana treść',
image: fileObject // To działa!
});
Middleware Laravel przechwytuje pole _method i traktuje żądanie tak, jakby zostało wysłane określoną metodą HTTP.
Przykłady implementacji
Operacja Create (standardowy POST)
Frontend (React z Inertia.js):
import { useForm } from '@inertiajs/react';
function CreatePost() {
const { data, setData, post, processing, errors } = useForm({
title: '',
content: '',
image: null,
});
const handleSubmit = (e) => {
e.preventDefault();
post('/posts', {
forceFormData: true, // Zapewnia multipart/form-data
});
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={data.title}
onChange={e => setData('title', e.target.value)}
placeholder="Tytuł"
/>
<textarea
value={data.content}
onChange={e => setData('content', e.target.value)}
placeholder="Treść"
/>
<input
type="file"
onChange={e => setData('image', e.target.files[0])}
/>
<button type="submit" disabled={processing}>
Utwórz post
</button>
</form>
);
}
Backend (kontroler Laravel):
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
'image' => 'nullable|image|max:2048',
]);
if ($request->hasFile('image')) {
$validated['image'] = $request->file('image')->store('posts', 'public');
}
Post::create($validated);
return redirect()->route('posts.index')
->with('success', 'Post został utworzony pomyślnie!');
}
}
Operacja Update (POST z _method: PUT)
Frontend (React z Inertia.js):
import { useForm } from '@inertiajs/react';
function EditPost({ post }) {
const { data, setData, post: submit, processing, errors } = useForm({
title: post.title,
content: post.content,
image: null,
_method: 'PUT', // To jest kluczowe!
});
const handleSubmit = (e) => {
e.preventDefault();
// Uwaga: Używamy POST tutaj, nie PUT
submit(`/posts/${post.id}`, {
forceFormData: true,
});
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={data.title}
onChange={e => setData('title', e.target.value)}
placeholder="Tytuł"
/>
<textarea
value={data.content}
onChange={e => setData('content', e.target.value)}
placeholder="Treść"
/>
<input
type="file"
onChange={e => setData('image', e.target.files[0])}
/>
{post.image && (
<div>
<p>Aktualne zdjęcie:</p>
<img src={`/storage/${post.image}`} alt="Aktualny" width="200" />
</div>
)}
<button type="submit" disabled={processing}>
Zaktualizuj post
</button>
</form>
);
}
Backend (kontroler Laravel):
public function update(Request $request, Post $post)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
'image' => 'nullable|image|max:2048',
]);
// Obsługa przesyłania pliku
if ($request->hasFile('image')) {
// Usuń stary obrazek jeśli istnieje
if ($post->image) {
Storage::disk('public')->delete($post->image);
}
$validated['image'] = $request->file('image')->store('posts', 'public');
}
$post->update($validated);
return redirect()->route('posts.index')
->with('success', 'Post został zaktualizowany pomyślnie!');
}
Trasy (web.php):
Route::resource('posts', PostController::class);
Kluczowe różnice między Create a Update
| Aspekt | Create (POST) | Update (POST + _method: PUT) |
|---|---|---|
| Metoda HTTP | POST | POST (z _method: 'PUT') |
| Trasa | /posts | /posts/{id} |
| Dane formularza | Standardowe pola | Standardowe pola + _method |
| Metoda Laravel | store() | update() |
| Cel | Utworzenie nowego zasobu | Aktualizacja istniejącego zasobu |
Ważne uwagi konfiguracyjne
1. Konfiguracja Inertia.js
Upewnij się, że używasz forceFormData: true przy obsłudze plików:
submit(`/posts/${post.id}`, {
forceFormData: true, // To zapewnia prawidłowe kodowanie multipart
});
2. Walidacja plików
Zawsze poprawnie waliduj przesyłane pliki:
$request->validate([
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
]);
Częste pułapki i rozwiązania
Pułapka 1: Używanie rzeczywistego żądania PUT dla plików
// ❌ To nie zadziała prawidłowo z plikami
router.put('/posts/1', formData);
// ✅ Użyj tego zamiast
router.post('/posts/1', {
...formData,
_method: 'PUT'
});
Pułapka 2: Zapomnienie o forceFormData
// ❌ Pliki mogą nie zostać przesłane prawidłowo
submit(`/posts/${post.id}`);
// ✅ Zawsze używaj forceFormData z plikami
submit(`/posts/${post.id}`, {
forceFormData: true
});
Pułapka 3: Nieobsługiwanie istniejących plików
// ❌ To spowoduje utratę istniejącego obrazka jeśli nie zostanie przesłany nowy
public function update(Request $request, Post $post)
{
$post->update($request->validated());
}
// ✅ Prawidłowo obsłuż aktualizacje plików
public function update(Request $request, Post $post)
{
$validated = $request->validated();
if ($request->hasFile('image')) {
// Usuń stary obrazek
if ($post->image) {
Storage::disk('public')->delete($post->image);
}
$validated['image'] = $request->file('image')->store('posts', 'public');
}
$post->update($validated);
}
Podsumowanie
Użycie POST z _method: "PUT" w przesyłaniu plików Laravel nie jest tylko konwencją - to konieczne obejście ograniczeń przeglądarek i protokołu HTTP. To podejście:
- Zachowuje zasady RESTful poprzez method spoofing
- Zapewnia prawidłową obsługę plików przez kodowanie multipart/form-data
- Dostarcza spójny routing z kontrolerami zasobów Laravel
- Działa bezproblemowo z Inertia.js i nowoczesnymi frameworkami frontend
Zrozumienie tego wzorca jest kluczowe dla każdego programisty Laravel pracującego z przesyłaniem plików. Choć może wydawać się nieintuicyjne na początku, to standardowy sposób obsługi aktualizacji plików w nowoczesnych aplikacjach webowych.
Pamiętaj: gdy masz wątpliwości dotyczące przesyłania plików w aktualizacjach Laravel, zawsze używaj POST z polem _method - twoje przyszłe ja będzie ci wdzięczne!