Laravel for beginnersLaravel สำหรับผู้เริ่มต้น
Laravel Beginner Guide
Build a task list, one request at a timeสร้างแอปรายการงาน ทีละคำขอ
Laravel is the most popular PHP framework. In this guide you build one small app, a task list where you can add, tick off and delete tasks. Along the way you learn the parts every Laravel app is made of: routes, migrations, models, controllers, Blade views, validation and tests. Laravel คือเฟรมเวิร์ก PHP ที่ได้รับความนิยมมากที่สุด ในคู่มือนี้คุณจะสร้างแอปเล็กๆ หนึ่งตัว คือรายการงานที่เพิ่ม ติ๊กว่าเสร็จ และลบงานได้ ระหว่างทางจะได้เรียนส่วนประกอบที่ทุกแอป Laravel มี ได้แก่ route, migration, โมเดล, controller, view ของ Blade, validation และเทสต์
- Frameworkเฟรมเวิร์ก Laravel 13.34.0
- Run onรันบน PHP 8.3.33
- Testsเทสต์ 10 passingผ่าน 10 ตัว
Requestsคำขอ
Routes send each URL to the right code.route ส่ง URL แต่ละอันไปยังโค้ดที่ถูกต้อง
Lessons 1–3 →บทที่ 1–3 →Dataข้อมูล
Migrations build the table; Eloquent reads and writes it.migration สร้างตาราง และ Eloquent อ่านเขียนข้อมูล
Lessons 4–5 →บทที่ 4–5 →Pages & testsหน้าเว็บและเทสต์
Controllers, Blade, validation, and tests that prove it all works.controller, Blade, validation และเทสต์ที่พิสูจน์ว่าทุกอย่างทำงาน
Lessons 6–9 →บทที่ 6–9 →Lesson 1 · The big pictureบทที่ 1 · ภาพรวม
How Laravel worksLaravel ทำงานอย่างไร
In plain PHP, every page is its own file that does everything: read input, query the database, print HTML. Laravel splits that work into small pieces that each live in a known folder, and connects them for you. Learn where each piece goes and you can find your way around any Laravel project. ใน PHP ล้วน แต่ละหน้าคือไฟล์หนึ่งไฟล์ที่ทำทุกอย่างเอง ทั้งอ่านข้อมูล คิวรีฐานข้อมูล และพิมพ์ HTML Laravel แบ่งงานเหล่านั้นเป็นชิ้นเล็กๆ ที่แต่ละชิ้นอยู่ในโฟลเดอร์ที่กำหนดไว้ แล้วเชื่อมทุกชิ้นให้เรา ถ้ารู้ว่าแต่ละชิ้นอยู่ที่ไหน ก็จะเข้าใจโปรเจกต์ Laravel ได้ทุกโปรเจกต์
| Pieceส่วนประกอบ | Lives inอยู่ที่ | Jobหน้าที่ |
|---|---|---|
| RouteRoute | routes/web.php | matches a method and URL to codeจับคู่ method และ URL กับโค้ด |
| ControllerController | app/Http/Controllers | handles the request: reads input, uses models, returns a pageจัดการคำขอ: อ่านข้อมูล ใช้โมเดล แล้วคืนหน้าเว็บ |
| Modelโมเดล | app/Models | one class per database tableหนึ่งคลาสต่อหนึ่งตารางในฐานข้อมูล |
| MigrationMigration | database/migrations | creates and changes tablesสร้างและแก้ไขตาราง |
| ViewView | resources/views | HTML templates written in Bladeเทมเพลต HTML ที่เขียนด้วย Blade |
| Testเทสต์ | tests/Feature | sends fake requests and checks the resultsส่งคำขอจำลองแล้วตรวจผล |
PHP 8.3 or newer and Composer. On Windows and macOS, Laravel Herd installs both in one go. Node.js is only needed later, for building CSS and JavaScript with Vite; this guide doesn't use it.PHP 8.3 ขึ้นไป และ Composer บน Windows และ macOS ติดตั้งทั้งสองอย่างได้ในครั้งเดียวด้วย Laravel Herd ส่วน Node.js ต้องใช้ภายหลังสำหรับ build CSS และ JavaScript ด้วย Vite ซึ่งคู่มือนี้ไม่ได้ใช้
Lesson 2 · Setupบทที่ 2 · ติดตั้ง
Install & first runติดตั้งและรันครั้งแรก
Composer downloads Laravel and everything it depends on, creates a .env settings file, makes an SQLite database and runs the first migrations, all in one command.
Composer ดาวน์โหลด Laravel และทุกอย่างที่มันต้องใช้ สร้างไฟล์ตั้งค่า .env สร้างฐานข้อมูล SQLite และรัน migration ชุดแรก ทั้งหมดในคำสั่งเดียว
$ composer create-project laravel/laravel taskapp Creating a "laravel/laravel" project at "./taskapp" Installing laravel/laravel (v13.10.1) - Installing laravel/laravel (v13.10.1): Extracting archive Created project in ~/taskapp ⋯ 246 lines of package downloads ⋯ No security vulnerability advisories found. > @php artisan key:generate --ansi INFO Application key set successfully. > @php -r "file_exists('database/database.sqlite') || touch('database/database.sqlite');" > @php artisan migrate --graceful --ansi INFO Preparing database. Creating migration table ................................................. 5.71ms DONE INFO Running migrations. 0001_01_01_000000_create_users_table ..................................... 1.95ms DONE 0001_01_01_000001_create_cache_table ..................................... 1.11ms DONE 0001_01_01_000002_create_jobs_table ...................................... 1.66ms DONE $ cd taskapp && php artisan --version Laravel Framework 13.34.0
The package list in the middle is collapsed. Folder paths are shortened to ~.รายการแพ็กเกจตรงกลางถูกย่อไว้ พาธโฟลเดอร์ถูกย่อเป็น ~
php artisan is Laravel's command-line tool; you'll use it in every lesson. Start the development server, then open http://127.0.0.1:8000 in your browser.
php artisan คือเครื่องมือ command line ของ Laravel ที่จะได้ใช้ในทุกบท เปิดเซิร์ฟเวอร์สำหรับพัฒนา แล้วเปิด http://127.0.0.1:8000 ในเบราว์เซอร์
$ php artisan serve INFO Server running on [http://127.0.0.1:8000]. Press Ctrl+C to stop the server 2026-10-05 22:15:28 / ...................................................... ~ 0.07ms
$ curl -s -o /dev/null -w "%{http_code} %{content_type}\n" http://127.0.0.1:8000/ 200 text/html; charset=utf-8
The project has many folders, but you'll spend most of your time in a handful: โปรเจกต์มีหลายโฟลเดอร์ แต่เวลาส่วนใหญ่จะอยู่กับไม่กี่โฟลเดอร์นี้:
| Pathพาธ | What's insideมีอะไรอยู่ |
|---|---|
app/ | your PHP classes: controllers, modelsคลาส PHP ของเรา: controller และโมเดล |
routes/web.php | the list of URLs your app answersรายการ URL ที่แอปตอบกลับ |
resources/views/ | Blade templatesเทมเพลต Blade |
database/ | migrations, factories, and the SQLite filemigration, factory และไฟล์ SQLite |
tests/ | your testsเทสต์ของเรา |
public/ | the web root: index.php, images, built CSSโฟลเดอร์หลักของเว็บ: index.php รูปภาพ และ CSS ที่ build แล้ว |
.env | settings and secrets for this machineการตั้งค่าและความลับของเครื่องนี้ |
vendor/ | Laravel itself and other packages; never editตัว Laravel และแพ็กเกจอื่นๆ ห้ามแก้ไข |
These lines of the new .env matter most. A fresh Laravel 13 app uses SQLite, so there is no database server to set up.
บรรทัดเหล่านี้ใน .env ที่สร้างใหม่สำคัญที่สุด แอป Laravel 13 ใหม่ใช้ SQLite จึงไม่ต้องตั้งค่าเซิร์ฟเวอร์ฐานข้อมูลเลย
.env also holds APP_KEY and database passwords. It's in .gitignore already; never commit it. On a live server set APP_DEBUG=false, or error pages will show your code and settings to visitors..env ยังเก็บ APP_KEY และรหัสผ่านฐานข้อมูลด้วย ไฟล์นี้อยู่ใน .gitignore แล้ว ห้าม commit เด็ดขาด บนเซิร์ฟเวอร์จริงให้ตั้ง APP_DEBUG=false ไม่อย่างนั้นหน้า error จะเปิดเผยโค้ดและการตั้งค่าให้ผู้เข้าชมเห็น
Lesson 3 · Requestsบทที่ 3 · คำขอ
RoutesRoute
A route says: "when a request with this method and this URL arrives, run this code". Add two routes to routes/web.php:
route บอกว่า "เมื่อมีคำขอที่ใช้ method นี้และ URL นี้เข้ามา ให้รันโค้ดนี้" เพิ่มสอง route ใน routes/web.php:
{name}is a route parameter. It matches one part of the URL, and Laravel passes it to the function as$name.{name}คือพารามิเตอร์ของ route ซึ่งตรงกับ URL หนึ่งช่วง และ Laravel ส่งค่านั้นให้ฟังก์ชันเป็น$name- Return a string and Laravel sends it as HTML. Return an array and it sends JSON.ถ้าคืนค่าเป็นข้อความ Laravel จะส่งเป็น HTML ถ้าคืนเป็นอาร์เรย์จะส่งเป็น JSON
$ curl -i http://127.0.0.1:8000/hello/Mya HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Cache-Control: no-cache, private Hello, Mya! $ curl -i http://127.0.0.1:8000/about HTTP/1.1 200 OK Cache-Control: no-cache, private Content-Type: application/json {"app":"Task list","version":1} $ curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/hello 404 $ php artisan route:list --except-vendor GET|HEAD / .................................................... routes/web.php:5 GET|HEAD about ............................................... routes/web.php:13 GET|HEAD hello/{name} ......................................... routes/web.php:9 Showing [3] routes
Cookie, date and server headers are left out of the -i output. /hello without a name gives 404: the {name} part is required. route:list shows every route and the file and line that defines it.ส่วนหัวคุกกี้ วันที่ และเซิร์ฟเวอร์ ถูกตัดออกจากผลลัพธ์ของ -i ส่วน /hello ที่ไม่มีชื่อได้ 404 เพราะ {name} เป็นส่วนที่ต้องมี route:list แสดง route ทั้งหมดพร้อมไฟล์และบรรทัดที่ประกาศ
| RouteRoute | Used forใช้สำหรับ |
|---|---|
Route::get('/tasks', …) | show a pageแสดงหน้าเว็บ |
Route::post('/tasks', …) | receive a form and create somethingรับฟอร์มแล้วสร้างข้อมูล |
Route::patch('/tasks/{task}', …) | change somethingแก้ไขข้อมูล |
Route::delete('/tasks/{task}', …) | remove somethingลบข้อมูล |
->name('tasks.index') | a name to link to with route('tasks.index')ตั้งชื่อเพื่อลิงก์ด้วย route('tasks.index') |
{name?} | an optional parameter (give $name a default)พารามิเตอร์ที่ไม่บังคับ (ต้องกำหนดค่าเริ่มต้นให้ $name) |
The /hello/{name} route prints $name without escaping. That's fine for a first test, but for anything a visitor controls, return a Blade view (lesson 6) or wrap it in e($name).route /hello/{name} พิมพ์ $name โดยไม่ escape ซึ่งใช้ได้สำหรับการลองครั้งแรก แต่ถ้าเป็นค่าที่ผู้เข้าชมกำหนดเอง ให้คืน view ของ Blade (บทที่ 6) หรือครอบด้วย e($name)
Lesson 4 · Dataบทที่ 4 · ข้อมูล
Migrations & modelsMigration และโมเดล
One artisan command creates the three files a new kind of data needs: the model (-m adds a migration, -f a factory).
คำสั่ง artisan เดียวสร้างสามไฟล์ที่ข้อมูลชนิดใหม่ต้องมี คือโมเดล (-m เพิ่ม migration และ -f เพิ่ม factory)
$ php artisan make:model Task -mf INFO Model [app/Models/Task.php] created successfully. INFO Factory [database/factories/TaskFactory.php] created successfully. INFO Migration [database/migrations/2026_10_05_151537_create_tasks_table.php] created successfully.
A migration describes a table in PHP, so everyone on the team can build the same database with one command. Add the two columns a task needs: migration อธิบายตารางด้วยโค้ด PHP ทุกคนในทีมจึงสร้างฐานข้อมูลแบบเดียวกันได้ด้วยคำสั่งเดียว เพิ่มสองคอลัมน์ที่งานแต่ละงานต้องมี:
$ php artisan migrate INFO Running migrations. 2026_10_05_151537_create_tasks_table ..................................... 1.84ms DONE
The model is the PHP class for that table. Laravel connects them by name: the Task model uses the tasks table automatically.
โมเดล คือคลาส PHP ของตารางนั้น Laravel เชื่อมทั้งสองด้วยชื่อ โมเดล Task จะใช้ตาราง tasks โดยอัตโนมัติ
#[Fillable([...])]lists the columns thatTask::create([...])may fill. Anything else in the array is quietly ignored (see lesson 11).#[Fillable([...])]ระบุคอลัมน์ที่Task::create([...])ใส่ค่าได้ ค่าอื่นในอาร์เรย์จะถูกละเว้นโดยไม่แจ้งเตือน (ดูบทที่ 11)casts()turns the database's0/1into a realtrue/false.casts()แปลง0/1จากฐานข้อมูลให้เป็นtrue/falseจริงๆ
The factory describes a fake task. Tests use it to create data in one line (lesson 8). factory อธิบายงานปลอมหนึ่งงาน เทสต์ใช้มันสร้างข้อมูลได้ในบรรทัดเดียว (บทที่ 8)
| Migration codeโค้ดใน migration | Columnคอลัมน์ |
|---|---|
$table->id() | auto-numbered primary key idprimary key id ที่เพิ่มหมายเลขอัตโนมัติ |
$table->string('title') | short text, required by defaultข้อความสั้น ต้องมีค่าเป็นค่าเริ่มต้น |
->nullable() | allow the column to be emptyอนุญาตให้คอลัมน์ว่างได้ |
$table->boolean('done')->default(false) | true/false with a defaultจริง/เท็จ พร้อมค่าเริ่มต้น |
$table->timestamps() | created_at and updated_at, filled for youcreated_at และ updated_at ที่ระบบเติมให้ |
Lesson 5 · Dataบทที่ 5 · ข้อมูล
Eloquent with TinkerEloquent ด้วย Tinker
Eloquent is Laravel's way of working with the database through models: you call methods, and it writes the SQL. Tinker (php artisan tinker) lets you try any of it against your real local database. Here it runs with --execute, so the exact code and output can be shown.
Eloquent คือวิธีทำงานกับฐานข้อมูลผ่านโมเดลของ Laravel เราเรียก method แล้วมันเขียน SQL ให้ ส่วน Tinker (php artisan tinker) ให้เราลองโค้ดเหล่านี้กับฐานข้อมูลจริงในเครื่องได้ ในที่นี้รันด้วย --execute เพื่อแสดงโค้ดและผลลัพธ์ได้ตรงตัว
$ php artisan tinker --execute="\$task = App\Models\Task::create(['title' => 'Learn routes']); echo \$task->toJson(JSON_PRETTY_PRINT);" { "title": "Learn routes", "updated_at": "2026-10-05T15:15:45.000000Z", "created_at": "2026-10-05T15:15:45.000000Z", "id": 1 }
done is missing because the database filled in its default; the object in memory never saw it. $task->fresh() would reload the row.done ไม่อยู่ใน JSON เพราะฐานข้อมูลเป็นคนใส่ค่าเริ่มต้น ออบเจกต์ในหน่วยความจำจึงไม่รู้ $task->fresh() จะโหลดแถวนั้นใหม่
$ php artisan tinker --execute="$(cat tinker-query.php)" 3 tasks 2 not done ["Write a migration","Try Tinker"]
| EloquentEloquent | Doesทำอะไร |
|---|---|
Task::create([...]) | insert a row and return the modelเพิ่มแถวแล้วคืนโมเดล |
Task::find(1) | the row with id 1, or nullแถวที่มี id 1 หรือ null |
Task::findOrFail(1) | the row, or a 404 pageแถวนั้น หรือหน้า 404 |
Task::where('done', false)->get() | all matching rows, as a Collectionทุกแถวที่ตรง ในรูป Collection |
Task::orderBy('id')->first() | the first row in that orderแถวแรกตามลำดับนั้น |
Task::count() | how many rowsจำนวนแถว |
$task->update([...]) | change and save columnsแก้ไขและบันทึกคอลัมน์ |
$task->delete() | remove the rowลบแถว |
Nothing reaches the database until a method like get(), first() or count() runs. Until then where() and orderBy() just build the query, so you can chain as many as you need.จะยังไม่มีอะไรถูกส่งไปยังฐานข้อมูลจนกว่าจะเรียก method อย่าง get(), first() หรือ count() ก่อนหน้านั้น where() และ orderBy() แค่สร้างคิวรี จึงต่อกันได้มากเท่าที่ต้องการ
Lesson 6 · Pagesบทที่ 6 · หน้าเว็บ
Controllers & BladeController และ Blade
Closures in routes/web.php get messy quickly. A controller groups the code for one kind of thing in a class, one method per action, and each route points at a method.
closure ใน routes/web.php จะรกได้เร็ว controller รวมโค้ดของเรื่องหนึ่งไว้ในคลาสเดียว หนึ่ง method ต่อหนึ่งการกระทำ และแต่ละ route ชี้ไปที่ method หนึ่งตัว
$ php artisan make:controller TaskController INFO Controller [app/Http/Controllers/TaskController.php] created successfully.
index()loads the tasks, unfinished first, and hands them to the view as$tasks.index()โหลดงานทั้งหมดโดยให้งานที่ยังไม่เสร็จขึ้นก่อน แล้วส่งให้ view เป็น$tasksstore()creates a task from the form, then redirects. Never return a page straight after a POST: refreshing would submit the form twice.store()สร้างงานจากฟอร์ม แล้ว redirect ห้ามคืนหน้าเว็บทันทีหลัง POST เพราะการรีเฟรชจะส่งฟอร์มซ้ำupdate(Task $task)uses route model binding: because the parameter is typedTaskand named like{task}, Laravel loads task 2 for/tasks/2, or answers 404 if it doesn't exist.update(Task $task)ใช้ route model binding: เพราะพารามิเตอร์ระบุชนิดเป็นTaskและชื่อตรงกับ{task}Laravel จึงโหลดงานที่ 2 ให้เมื่อเรียก/tasks/2หรือตอบ 404 ถ้าไม่มีงานนั้น
The view is a Blade template: HTML with a few extras. view('tasks.index') means resources/views/tasks/index.blade.php.
view คือเทมเพลต Blade ซึ่งก็คือ HTML ที่มีความสามารถเพิ่มเล็กน้อย view('tasks.index') หมายถึง resources/views/tasks/index.blade.php
| BladeBlade | Doesทำอะไร |
|---|---|
{{ $x }} | print a value, escaped for HTMLพิมพ์ค่า โดย escape HTML ให้ |
@foreach … @endforeach | a loopลูป |
@forelse … @empty … @endforelse | a loop with a "nothing here" partลูปที่มีส่วน "ไม่มีข้อมูล" |
@if … @endif | a conditionเงื่อนไข |
@csrf | the hidden security token every form needstoken ความปลอดภัยที่ซ่อนไว้ ซึ่งทุกฟอร์มต้องมี |
@method('PATCH') | send PATCH or DELETE from an HTML formส่ง PATCH หรือ DELETE จากฟอร์ม HTML |
Now real requests against the running app. jar.txt keeps the session cookie between requests, and $TOKEN holds the _token value copied from the first page.
ต่อไปคือคำขอจริงที่ส่งไปยังแอปที่กำลังทำงาน jar.txt เก็บคุกกี้ session ระหว่างคำขอ และ $TOKEN เก็บค่า _token ที่คัดลอกจากหน้าแรก
$ curl -s -c jar.txt http://127.0.0.1:8000/tasks <!doctype html> <title>Tasks</title> <h1>Tasks (3)</h1> <form method="POST" action="http://127.0.0.1:8000/tasks"> <input type="hidden" name="_token" value="Yl0fPufZEHnlurrnz4nYELvjbSdJpzCF7Lgix6mv" autocomplete="off"> <input name="title" placeholder="New task"> <button>Add</button> </form> <ul> <li> <form method="POST" action="http://127.0.0.1:8000/tasks/2"> <input type="hidden" name="_token" value="Yl0fPufZEHnlurrnz4nYELvjbSdJpzCF7Lgix6mv" autocomplete="off"> <input type="hidden" name="_method" value="PATCH"> <button>[ ]</button> Write a migration </form> </li> <li> <form method="POST" action="http://127.0.0.1:8000/tasks/3"> <input type="hidden" name="_token" value="Yl0fPufZEHnlurrnz4nYELvjbSdJpzCF7Lgix6mv" autocomplete="off"> <input type="hidden" name="_method" value="PATCH"> <button>[ ]</button> Try Tinker </form> </li> <li> <form method="POST" action="http://127.0.0.1:8000/tasks/1"> <input type="hidden" name="_token" value="Yl0fPufZEHnlurrnz4nYELvjbSdJpzCF7Lgix6mv" autocomplete="off"> <input type="hidden" name="_method" value="PATCH"> <button>[x]</button> Learn routes </form> </li> </ul> $ curl -si -b jar.txt -c jar.txt -X POST -d "_token=$TOKEN" -d "title=Style the page" http://127.0.0.1:8000/tasks | grep -i "^HTTP\|^location" HTTP/1.1 302 Found Location: http://127.0.0.1:8000/tasks $ curl -si -b jar.txt -c jar.txt -X POST -d "_token=$TOKEN" -d "_method=PATCH" http://127.0.0.1:8000/tasks/2 | grep -i "^HTTP\|^location" HTTP/1.1 302 Found Location: http://127.0.0.1:8000/tasks $ curl -s -o /dev/null -w "%{http_code}\n" -b jar.txt -c jar.txt -X POST -d "_token=$TOKEN" -d "title=" http://127.0.0.1:8000/tasks 500 $ grep -o "SQLSTATE[^(]*" storage/logs/laravel.log | head -1 SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: tasks.title
The last request sent an empty title and crashed the app with a 500 error. Laravel turns empty form fields into null, and the migration said title can't be null. The next lesson fixes this properly.คำขอสุดท้ายส่งชื่องานว่าง ทำให้แอปพังด้วยข้อผิดพลาด 500 Laravel แปลงช่องฟอร์มที่ว่างให้เป็น null แต่ migration กำหนดว่า title เป็น null ไม่ได้ บทถัดไปจะแก้ปัญหานี้อย่างถูกวิธี
Lesson 7 · Inputบทที่ 7 · ข้อมูลขาเข้า
Forms & validationฟอร์มและ validation
Never trust form input. $request->validate() checks each field against a list of rules. If any rule fails, Laravel stops right there, sends the visitor back to the form with error messages and what they typed, and the rest of your method never runs.
อย่าไว้ใจข้อมูลจากฟอร์ม $request->validate() ตรวจแต่ละฟิลด์ตามรายการกฎ ถ้ามีกฎข้อใดไม่ผ่าน Laravel จะหยุดทันที ส่งผู้เข้าชมกลับไปที่ฟอร์มพร้อมข้อความผิดพลาดและสิ่งที่พิมพ์ไว้ และโค้ดที่เหลือใน method จะไม่ทำงาน
- Fails: Laravel saves the error messages and the old input in the session and redirects back (302).ไม่ผ่าน: Laravel เก็บข้อความผิดพลาดและข้อมูลเดิมไว้ใน session แล้ว redirect กลับ (302)
@error('title')shows that field's message, andold('title')puts back what they typed.@error('title')แสดงข้อความของฟิลด์นั้น และold('title')เติมสิ่งที่พิมพ์ไว้กลับเข้าไป- Passes:
$validatedholds only the checked fields. The task is created, and->with('status', …)flashes a message that's shown once.ผ่าน:$validatedมีเฉพาะฟิลด์ที่ตรวจแล้ว งานถูกสร้าง และ->with('status', …)แฟลชข้อความที่แสดงเพียงครั้งเดียว
$ curl -si -b jar.txt -c jar.txt -X POST -d "_token=$TOKEN" -d "title=" http://127.0.0.1:8000/tasks | grep -i "^HTTP\|^location" HTTP/1.1 302 Found Location: http://127.0.0.1:8000/tasks $ curl -s -b jar.txt -c jar.txt http://127.0.0.1:8000/tasks | sed -n '1,12p' <!doctype html> <title>Tasks</title> <h1>Tasks (4)</h1> <form method="POST" action="http://127.0.0.1:8000/tasks"> <input type="hidden" name="_token" value="syhvKtkw4XgOGtEs0t1svQonuivpjDOAWMMyP7C7" autocomplete="off"> <input name="title" value="" placeholder="New task"> <button>Add</button> <p class="error">The title field is required.</p> </form> <ul> $ curl -s -o /dev/null -b jar.txt -c jar.txt -X POST -d "_token=$TOKEN" -d "title=$LONG" http://127.0.0.1:8000/tasks $ curl -s -b jar.txt -c jar.txt http://127.0.0.1:8000/tasks | grep "error" <p class="error">The title field must not be greater than 100 characters.</p> $ curl -si -b jar.txt -c jar.txt -X POST -d "_token=$TOKEN" -d "title=Add validation" http://127.0.0.1:8000/tasks | grep -i "^HTTP\|^location" HTTP/1.1 302 Found Location: http://127.0.0.1:8000/tasks $ curl -s -b jar.txt -c jar.txt http://127.0.0.1:8000/tasks | sed -n '1,8p' <!doctype html> <title>Tasks</title> <h1>Tasks (5)</h1> <p class="status">Task added.</p> <form method="POST" action="http://127.0.0.1:8000/tasks"> <input type="hidden" name="_token" value="syhvKtkw4XgOGtEs0t1svQonuivpjDOAWMMyP7C7" autocomplete="off"> <input name="title" value="" placeholder="New task"> $ curl -s -o /dev/null -w "%{http_code}\n" -b jar.txt -c jar.txt -X POST -d "_token=$TOKEN" --data-urlencode "title= " http://127.0.0.1:8000/tasks 302 $ curl -s -b jar.txt -c jar.txt http://127.0.0.1:8000/tasks | grep -E "error|<h1>" <h1>Tasks (5)</h1> <p class="error">The title field is required.</p>
$LONG is 101 letters. The last two requests send a title of three spaces. It fails required too, because Laravel trims every input and then turns empty strings into null, so the count stays at 5.$LONG คือตัวอักษร 101 ตัว สองคำขอสุดท้ายส่งชื่องานที่เป็นช่องว่างสามตัว ซึ่งก็ไม่ผ่าน required เช่นกัน เพราะ Laravel ตัดช่องว่างของทุกค่าที่ส่งมา แล้วแปลงข้อความว่างเป็น null จำนวนงานจึงยังเป็น 5
| Ruleกฎ | Checksตรวจอะไร |
|---|---|
required | present and not emptyต้องมีและไม่ว่าง |
string integer boolean | the type of valueชนิดของค่า |
max:100 min:3 | length for text, size for numbersความยาวของข้อความ หรือขนาดของตัวเลข |
email | looks like an email addressมีรูปแบบเป็นอีเมล |
unique:tasks,title | no other row has this value (practice task 2)ไม่มีแถวอื่นที่มีค่านี้ (แบบฝึกหัดที่ 2) |
nullable | may be left emptyเว้นว่างได้ |
Lesson 8 · Confidenceบทที่ 8 · ความมั่นใจ
Feature testsFeature test
Clicking through every page after each change gets old fast. A feature test sends fake requests to your app and checks the response and the database, with no browser and no server. Here the test for deleting a task was written before the delete feature existed. การคลิกทุกหน้าหลังแก้โค้ดทุกครั้งน่าเบื่อมาก feature test ส่งคำขอจำลองเข้าไปในแอป แล้วตรวจคำตอบและฐานข้อมูล โดยไม่ต้องมีเบราว์เซอร์หรือเซิร์ฟเวอร์ ในที่นี้เทสต์สำหรับการลบงานถูกเขียนก่อนที่ฟีเจอร์ลบจะมีอยู่จริง
$ php artisan make:test TaskTest INFO Test [tests/Feature/TaskTest.php] created successfully.
| In a testในเทสต์ | Doesทำอะไร |
|---|---|
use RefreshDatabase; | every test starts with empty tables (an in-memory SQLite database, set in phpunit.xml)ทุกเทสต์เริ่มด้วยตารางว่าง (ฐานข้อมูล SQLite ในหน่วยความจำ ตั้งค่าไว้ใน phpunit.xml) |
Task::factory()->create() | insert a fake taskเพิ่มงานปลอม |
$this->get('/tasks') | send a fake request (also post, patch, delete)ส่งคำขอจำลอง (มี post, patch, delete ด้วย) |
assertOk() assertRedirect() | check the status codeตรวจรหัสสถานะ |
assertSee('…') | check the page contains this textตรวจว่าหน้าเว็บมีข้อความนี้ |
assertSessionHasErrors('title') | check validation failed for a fieldตรวจว่า validation ของฟิลด์นั้นไม่ผ่าน |
assertDatabaseHas('tasks', [...]) | check a matching row existsตรวจว่ามีแถวที่ตรงอยู่ |
No CSRF token is needed: Laravel skips that check while tests run. Now run the tests. The last one fails, because there's no route for DELETE yet: ไม่ต้องใช้ CSRF token เพราะ Laravel ข้ามการตรวจนี้ระหว่างรันเทสต์ ทีนี้รันเทสต์ เทสต์สุดท้ายไม่ผ่าน เพราะยังไม่มี route สำหรับ DELETE:
$ php artisan test PASS Tests\Unit\ExampleTest ✓ that true is true PASS Tests\Feature\ExampleTest ✓ the application returns a successful response 0.06s FAIL Tests\Feature\TaskTest ✓ the list shows tasks 0.05s ✓ a task can be created 0.01s ✓ the title is required ✓ a task can be marked done ⨯ a task can be deleted 0.07s ────────────────────────────────────────────────────────────────────────────────────── FAILED Tests\Feature\TaskTest > a task can be deleted Expected response status code [201, 301, 302, 303, 307, 308] but received 405. Failed asserting that false is true. at tests/Feature/TaskTest.php:54 50▕ $task = Task::factory()->create(); 51▕ 52▕ $response = $this->delete("/tasks/{$task->id}"); 53▕ ➜ 54▕ $response->assertRedirect('/tasks'); 55▕ $this->assertModelMissing($task); 56▕ } 57▕ } 58▕ Tests: 1 failed, 6 passed (12 assertions) Duration: 0.35s
405 means "the URL exists, but not for this method": /tasks/{task} only answers PATCH so far.405 หมายถึง "URL นี้มีอยู่ แต่ไม่รับ method นี้" ตอนนี้ /tasks/{task} รับเฉพาะ PATCH
Add the route and the controller method: เพิ่ม route และ method ใน controller:
$ php artisan test PASS Tests\Unit\ExampleTest ✓ that true is true PASS Tests\Feature\ExampleTest ✓ the application returns a successful response 0.06s PASS Tests\Feature\TaskTest ✓ the list shows tasks 0.05s ✓ a task can be created 0.01s ✓ the title is required 0.01s ✓ a task can be marked done ✓ a task can be deleted Tests: 7 passed (14 assertions) Duration: 0.26s $ php artisan test --filter=deleted PASS Tests\Feature\TaskTest ✓ a task can be deleted 0.11s Tests: 1 passed (3 assertions) Duration: 0.15s
7 tests: the 5 task tests plus the 2 examples that come with Laravel. --filter runs only the tests whose names match.7 เทสต์ คือเทสต์ของ tasks 5 ตัว รวมกับเทสต์ตัวอย่าง 2 ตัวที่มากับ Laravel ส่วน --filter รันเฉพาะเทสต์ที่ชื่อตรงกัน
Lesson 9 · Your turnบทที่ 9 · ถึงตาคุณแล้ว
Practiceแบบฝึกหัด
Three small features for the task list. Each solution was added to the real app, with a test, and all 10 tests pass at the end. ฟีเจอร์เล็กๆ สามอย่างสำหรับแอปรายการงาน ทุกเฉลยถูกเพิ่มลงในแอปจริงพร้อมเทสต์ และเทสต์ทั้ง 10 ตัวผ่านในตอนท้าย
1. Count the finished tasks1. นับงานที่เสร็จแล้ว
Change the heading to say, for example, "Tasks (1 of 3 done)". Hint: a Collection has where() and count() too, so you don't need a second query.เปลี่ยนหัวข้อให้แสดง เช่น "Tasks (1 of 3 done)" คำใบ้: Collection ก็มี where() และ count() จึงไม่ต้องคิวรีซ้ำ
Show solutionดูเฉลย
2. No duplicate titles2. ห้ามชื่องานซ้ำ
Refuse a task whose title already exists, with the message "The title has already been taken."ปฏิเสธงานที่มีชื่อซ้ำกับที่มีอยู่แล้ว พร้อมข้อความ "The title has already been taken."
Show solutionดูเฉลย
3. Clear all completed tasks3. ล้างงานที่เสร็จแล้วทั้งหมด
Add a "Clear completed" button that sends DELETE /tasks/completed and deletes every finished task. Watch where you put the route.เพิ่มปุ่ม "Clear completed" ที่ส่ง DELETE /tasks/completed แล้วลบงานที่เสร็จแล้วทั้งหมด ระวังตำแหน่งที่วาง route
Show solutionดูเฉลย
My first attempt added the route at the bottom of the file. The new test failed with a 404, because /tasks/{task} matched first and looked for a task with the id "completed":ครั้งแรกผมเพิ่ม route ไว้ล่างสุดของไฟล์ เทสต์ใหม่ไม่ผ่านด้วย 404 เพราะ /tasks/{task} จับคู่ก่อน แล้วไปหางานที่มี id เป็น "completed":
$ php artisan test PASS Tests\Unit\ExampleTest ✓ that true is true PASS Tests\Feature\ExampleTest ✓ the application returns a successful response 0.07s FAIL Tests\Feature\TaskTest ✓ the list shows tasks 0.05s ✓ a task can be created 0.01s ✓ the title is required ✓ a task can be marked done ✓ a task can be deleted ✓ titles must be unique ✓ the heading counts done tasks ⨯ completed tasks can be cleared 0.01s ────────────────────────────────────────────────────────────────────────────────────── FAILED Tests\Feature\TaskTest > completed tasks can be cleared Expected response status code [201, 301, 302, 303, 307, 308] but received 404. Failed asserting that false is true. at tests/Feature/TaskTest.php:84 80▕ $open = Task::factory()->create(['done' => false]); 81▕ 82▕ $response = $this->delete('/tasks/completed'); 83▕ ➜ 84▕ $response->assertRedirect('/tasks'); 85▕ $this->assertDatabaseCount('tasks', 1); 86▕ $this->assertModelExists($open); 87▕ } 88▕ } Tests: 1 failed, 9 passed (18 assertions) Duration: 0.29s
Moving it above the {task} routes fixes it:ย้ายไปไว้เหนือ route ที่มี {task} ก็แก้ได้:
$ php artisan test PASS Tests\Unit\ExampleTest ✓ that true is true PASS Tests\Feature\ExampleTest ✓ the application returns a successful response 0.07s PASS Tests\Feature\TaskTest ✓ the list shows tasks 0.04s ✓ a task can be created 0.01s ✓ the title is required ✓ a task can be marked done ✓ a task can be deleted ✓ titles must be unique ✓ the heading counts done tasks ✓ completed tasks can be cleared Tests: 10 passed (21 assertions) Duration: 0.28s
Lesson 10 · Referenceบทที่ 10 · อ้างอิง
Artisan commandsคำสั่ง Artisan
The commands from this guide, plus a few you'll need soon. php artisan list shows them all, and php artisan help make:model explains any one.
คำสั่งที่ใช้ในคู่มือนี้ รวมกับอีกไม่กี่คำสั่งที่จะได้ใช้เร็วๆ นี้ php artisan list แสดงคำสั่งทั้งหมด และ php artisan help make:model อธิบายคำสั่งนั้นๆ
| Commandคำสั่ง | Doesทำอะไร |
|---|---|
php artisan serve | start the development serverเปิดเซิร์ฟเวอร์สำหรับพัฒนา |
php artisan route:list | every route, method and handlerroute ทั้งหมด พร้อม method และตัวจัดการ |
php artisan make:model Task -mf | model + migration + factoryโมเดล + migration + factory |
php artisan make:controller TaskController | an empty controller (--resource adds the 7 standard methods)controller ว่าง (--resource เพิ่ม method มาตรฐาน 7 ตัว) |
php artisan make:test TaskTest | a feature test (--unit for a unit test)feature test (--unit สำหรับ unit test) |
php artisan migrate | run new migrationsรัน migration ใหม่ |
php artisan migrate:rollback | undo the last batchย้อน migration ชุดล่าสุด |
php artisan migrate:fresh | drop every table and migrate again (deletes all data)ลบทุกตารางแล้ว migrate ใหม่ (ข้อมูลหายหมด) |
php artisan tinker | an interactive PHP shell inside your appPHP shell แบบโต้ตอบภายในแอป |
php artisan test | run the tests (--filter=name for some)รันเทสต์ (--filter=ชื่อ เพื่อรันบางตัว) |
php artisan optimize:clear | clear cached config, routes and viewsล้างแคชของ config, route และ view |
php artisan about | versions and settings of this appเวอร์ชันและการตั้งค่าของแอปนี้ |
Lesson 11 · Debuggingบทที่ 11 · การดีบัก
Common mistakesข้อผิดพลาดที่พบบ่อย
Each of these came up while building this app. The messages are the real ones. ทุกข้อด้านล่างเกิดขึ้นจริงระหว่างสร้างแอปนี้ ข้อความผิดพลาดก็เป็นของจริง
419 Page Expired419 Page Expired
The form has no @csrf, or the session expired. Laravel refuses the POST before your controller runs.ฟอร์มไม่มี @csrf หรือ session หมดอายุ Laravel จะปฏิเสธ POST ก่อนที่ controller จะทำงาน
$ curl -s -o /dev/null -w "%{http_code}\n" -X POST -d "title=No token" http://127.0.0.1:8000/tasks 419
"no such table""no such table"
The code is there but the table isn't: the migration hasn't been run on this database. It happens after cloning a project or switching databases. Run php artisan migrate.มีโค้ดแล้วแต่ยังไม่มีตาราง เพราะยังไม่ได้รัน migration กับฐานข้อมูลนี้ มักเกิดหลัง clone โปรเจกต์หรือเปลี่ยนฐานข้อมูล ให้รัน php artisan migrate
$ php artisan tinker --execute="echo App\Models\Task::count();" Illuminate\Database\QueryException SQLSTATE[HY000]: General error: 1 no such table: tasks (Connection: sqlite, Database: database/empty.sqlite, SQL: select count(*) as "aggregate" from "tasks").
A value that silently isn't savedค่าที่ไม่ถูกบันทึกโดยไม่มีการแจ้งเตือน
create() only fills columns listed in #[Fillable]. Anything else is dropped without an error. If a value goes missing, check that list first.create() ใส่ค่าเฉพาะคอลัมน์ที่อยู่ใน #[Fillable] ค่าอื่นจะถูกตัดทิ้งโดยไม่มี error ถ้าค่าหายไป ให้ตรวจรายการนี้ก่อน
$ php artisan tinker --execute="$(cat tinker-fillable.php)" { "id": 4, "title": "Buy milk", "done": false, "created_at": "2026-10-05T15:15:56.000000Z", "updated_at": "2026-10-05T15:15:56.000000Z" }
405 Method Not Allowed405 Method Not Allowed
The URL exists, but not for this method. Either the route is missing (lesson 8 hit exactly this), or an HTML form is sending POST without @method('PATCH') or @method('DELETE').URL นี้มีอยู่ แต่ไม่รับ method นี้ อาจเป็นเพราะยังไม่มี route (บทที่ 8 เจอแบบนี้พอดี) หรือฟอร์ม HTML ส่ง POST โดยไม่มี @method('PATCH') หรือ @method('DELETE')
A fixed URL caught by a {parameter} routeURL แบบตายตัวถูก route ที่มี {พารามิเตอร์} จับไปก่อน
Routes are matched from top to bottom. /tasks/{task} also matches /tasks/completed, so the fixed route must come first (practice task 3).route ถูกจับคู่จากบนลงล่าง /tasks/{task} ตรงกับ /tasks/completed ด้วย route แบบตายตัวจึงต้องอยู่ก่อน (แบบฝึกหัดที่ 3)
Printing with {!! !!}พิมพ์ค่าด้วย {!! !!}
{!! !!} prints raw HTML, so a task titled <script>… would run in every visitor's browser. Use {{ }}, which escapes, for anything a user typed.{!! !!} พิมพ์ HTML ดิบ งานที่ชื่อ <script>… จะทำงานในเบราว์เซอร์ของผู้เข้าชมทุกคน ให้ใช้ {{ }} ซึ่ง escape ให้ กับทุกอย่างที่ผู้ใช้พิมพ์
Lesson 12 · Referenceบทที่ 12 · อ้างอิง
Glossaryคำศัพท์
Laravel's documentation and error messages use the English terms, so it pays to know both. เอกสารและข้อความแจ้งข้อผิดพลาดของ Laravel ใช้คำภาษาอังกฤษ จึงควรรู้ทั้งสองภาษา
| English | ไทย | Meaningความหมาย |
|---|---|---|
| framework | เฟรมเวิร์ก | Ready-made parts and rules for building appsชุดส่วนประกอบและกติกาสำเร็จรูปสำหรับสร้างแอป |
| route | เส้นทาง (route) | A rule linking a method and URL to codeกฎที่จับคู่ method และ URL กับโค้ด |
| controller | คอนโทรลเลอร์ | A class that handles requestsคลาสที่จัดการคำขอ |
| model | โมเดล | A class for one database tableคลาสของหนึ่งตารางในฐานข้อมูล |
| migration | ไมเกรชัน | A PHP file that creates or changes tablesไฟล์ PHP ที่สร้างหรือแก้ไขตาราง |
| Eloquent | Eloquent | Laravel's way to query the database through modelsวิธีคิวรีฐานข้อมูลผ่านโมเดลของ Laravel |
| Blade | Blade | Laravel's template language for viewsภาษาเทมเพลตสำหรับ view ของ Laravel |
| Artisan | Artisan | Laravel's command-line tool, php artisanเครื่องมือ command line ของ Laravel คือ php artisan |
| middleware | มิดเดิลแวร์ | Code every request passes through, like the CSRF checkโค้ดที่ทุกคำขอต้องผ่าน เช่น การตรวจ CSRF |
| CSRF token | CSRF token | A secret in each form that proves it came from your siteค่าลับในแต่ละฟอร์มที่พิสูจน์ว่ามาจากเว็บของเรา |
| route model binding | route model binding | Laravel loading a model from the id in the URLการที่ Laravel โหลดโมเดลจาก id ใน URL ให้ |
| mass assignment | mass assignment | Filling many columns from one array; limited by #[Fillable]การใส่ค่าหลายคอลัมน์จากอาร์เรย์เดียว ซึ่งจำกัดด้วย #[Fillable] |
| validation | การตรวจสอบข้อมูล | Checking input against rules before using itการตรวจข้อมูลขาเข้าตามกฎก่อนนำไปใช้ |
| flash data | ข้อมูลแฟลช | Session data that lasts for one more requestข้อมูลใน session ที่อยู่ได้แค่คำขอถัดไปหนึ่งครั้ง |
| factory | แฟกทอรี | A recipe for fake model data in testsสูตรสร้างข้อมูลปลอมของโมเดลสำหรับเทสต์ |
| feature test | feature test | A test that sends fake requests to the appเทสต์ที่ส่งคำขอจำลองเข้าไปในแอป |