📚 Tutorial cho người mới · 2026
Học lập trình với GracePL
Hướng dẫn từng bước từ zero đến hero — dù bạn chưa bao giờ lập trình, hay đến từ PHP/Python/JavaScript, bạn đều có thể bắt đầu ở đây.
Bài 1 GracePL là gì?
🎯 Mục tiêu bài này: Hiểu GracePL là gì, tại sao nên học, và nó khác gì so với PHP/Python/JavaScript.
GracePL là ngôn ngữ lập trình production-ready được thiết kế để:
- Có cú pháp quen thuộc như PHP — dễ học, dễ đọc
- Chạy nhanh như Go — biên dịch sang bytecode, thực thi bởi VM viết bằng Go
- Tích hợp sẵn hơn 335 hàm — HTTP, Database, Redis, JWT, Crypto, WebSocket... không cần cài gói ngoài
- Hỗ trợ concurrency thực sự (goroutine + channel như Go)
So sánh nhanh
| Tính năng | PHP | Python | GracePL |
|---|---|---|---|
| Cú pháp | ✅ Quen thuộc | ✅ Đơn giản | ✅ Giống PHP |
| Tốc độ | ⚠️ Trung bình | ⚠️ Trung bình | ✅ Nhanh (Go VM) |
| HTTP server tích hợp | ❌ | ⚠️ Flask/FastAPI | ✅ Có sẵn |
| Database tích hợp | ❌ PDO/MySQLi | ❌ SQLAlchemy | ✅ Có sẵn |
| Concurrency | ❌ | ⚠️ asyncio | ✅ Goroutine+Channel |
| Cài đặt | ⚠️ Phức tạp | ⚠️ pip packages | ✅ 1 file executable |
💡 Bạn đã biết PHP? Tuyệt vời! GracePL dùng
$biến, function, foreach, class — bạn sẽ cảm thấy như ở nhà chỉ sau 5 phút.
✅ Đánh dấu đã học xong
Bài 2 Cài đặt GracePL
🎯 Mục tiêu: Cài GracePL và chạy được lệnh
gracepl trong terminal.
Bước 1: Tải về
bash
# Tải binary từ GitHub Releases
# Windows: gracepl.exe
# Linux/macOS: gracepl
# Hoặc build từ source (cần Go 1.21+)
git clone https://github.com/ghoullp/gracepl-lang
cd gracepl-lang
go build -o gracepl .
Bước 2: Thêm vào PATH
bash — Linux/macOS
sudo mv gracepl /usr/local/bin/
gracepl --version
powershell — Windows
# Copy gracepl.exe tới một thư mục trong PATH, ví dụ C:\tools\
.\gracepl.exe --version
Bước 3: Cài VS Code Extension (khuyến nghị)
Tìm kiếm "GracePL" trong VS Code Extension Marketplace để có:
- Syntax highlighting
- Code completion (IntelliSense)
- Tích hợp debugger
- Run script bằng phím F5
✅ Kiểm tra thành công: Chạy
gracepl --version trong terminal — nếu thấy số version là xong!
✅ Đánh dấu đã học xong
Bài 3 Hello World — Chương trình đầu tiên
🎯 Mục tiêu: Tạo và chạy file GracePL đầu tiên của bạn.
Tạo file hello.gp
Mọi file GracePL bắt đầu bằng thẻ mở <gp> — tương tự <?php trong PHP.
hello.gp
<gp>
echo "Xin chào thế giới!\n";
echo "Tôi đang học GracePL 🚀\n";
// Biến và nội suy chuỗi
$ten = "Alice";
$tuoi = 25;
echo "Tên: $ten, Tuổi: $tuoi\n";
// Cũng có thể dùng print
$pi = 3.14159;
echo "Pi = $pi\n";
Chạy script
terminal
gracepl run hello.gp
📝 Kết quả mong đợi
Xin chào thế giới! Tôi đang học GracePL 🚀 Tên: Alice, Tuổi: 25 Pi = 3.14159
💡 Chú ý:
\n là ký tự xuống dòng (newline). Nếu muốn in không xuống dòng, bỏ \n đi. Bạn cũng có thể dùng println("text") — tự động thêm xuống dòng.
✅ Đánh dấu đã học xong
Bài 4 Các lệnh CLI cơ bản
| Lệnh | Mô tả |
|---|---|
gracepl run script.gp | Chạy script |
gracepl run script.gp -- arg1 arg2 | Chạy với arguments |
gracepl check script.gp | Kiểm tra lỗi cú pháp không chạy |
gracepl debug script.gp | Chạy với VS Code debugger (DAP) |
gracepl test ./tests/ | Chạy tất cả test trong thư mục |
gracepl pkg install name | Cài package |
gracepl repl | Chế độ tương tác (REPL) |
REPL — Thử code trực tiếp
REPL (Read-Eval-Print Loop) là cách học nhanh nhất — gõ code và xem kết quả ngay:
terminal
$ gracepl repl
gracepl> $x = 10 + 20
gracepl> echo $x // 30
gracepl> strlen("hello") // 5
gracepl> exit
✅ Đánh dấu đã học xong
Bài 5 Biến & Kiểu dữ liệu
🎯 Mục tiêu: Hiểu các kiểu dữ liệu cơ bản và cách khai báo biến.
Biến trong GracePL bắt đầu bằng ký tự $. Không cần khai báo kiểu — ngôn ngữ tự nhận diện.
types.gp
<gp>
// ── Số nguyên (int) ──────────────────────────
$tuoi = 25;
$so_am = -10;
$hexa = 0xFF; // 255 — hệ thập lục phân
// ── Số thực (float) ──────────────────────────
$pi = 3.14159;
$gia = 99.99;
// ── Chuỗi (string) ───────────────────────────
$ten = "Alice";
$chao = 'Xin chào'; // nháy đơn không nội suy biến
$msg = "Tên tôi là $ten"; // nháy kép: nội suy $ten vào chuỗi
// ── Boolean ─────────────────────────────────
$dung = true;
$sai = false;
// ── Null ─────────────────────────────────────
$rong = null;
// ── Kiểm tra kiểu ───────────────────────────
echo gettype($tuoi); // "integer"
echo gettype($pi); // "double"
echo gettype($ten); // "string"
echo gettype($dung); // "boolean"
echo gettype($rong); // "NULL"
echo is_int($tuoi); // true
echo is_float($pi); // true
echo is_string($ten); // true
echo is_null($rong); // true
// ── Chuyển kiểu ─────────────────────────────
$str_num = "42";
$so = intval($str_num); // → 42 (int)
$fl = floatval("3.14"); // → 3.14 (float)
$st = strval(123); // → "123" (string)
Phạm vi biến (Scope)
scope.gp
<gp>
$toan_cuc = "Tôi ở ngoài function";
function sayHello() {
// ✅ Closure capture: dùng use để truy cập biến ngoài
echo "Hello!\n";
// KHÔNG thể truy cập $toan_cuc trực tiếp
}
// Dùng closure với use để capture biến ngoài
$greet = function($ten) use ($toan_cuc) {
echo "Xin chào $ten! ($toan_cuc)\n";
};
$greet("Bob");
✅ Đánh dấu đã học xong
Bài 6 Toán tử
operators.gp
<gp>
// ── Số học ───────────────────────────────────
$a = 10; $b = 3;
echo $a + $b; // 13
echo $a - $b; // 7
echo $a * $b; // 30
echo $a / $b; // 3.333...
echo $a % $b; // 1 (chia lấy dư)
echo $a ** $b; // 1000 (lũy thừa)
echo intdiv($a, $b); // 3 (chia nguyên)
// ── Gán kết hợp ──────────────────────────────
$x = 5;
$x += 3; // $x = 8
$x -= 2; // $x = 6
$x *= 4; // $x = 24
$x /= 3; // $x = 8
$x %= 3; // $x = 2
$x .= "abc"; // Nối chuỗi: "2abc"
// ── So sánh ──────────────────────────────────
echo (5 == "5"); // true (chỉ so giá trị)
echo (5 === "5"); // false (so giá trị VÀ kiểu)
echo (5 != 3); // true
echo (5 !== "5"); // true
echo (5 > 3); // true
echo (5 >= 5); // true
echo (3 < 5); // true
echo (3 <= 3); // true
// ── Logic ────────────────────────────────────
echo (true && false); // false (VÀ)
echo (true || false); // true (HOẶC)
echo (!true); // false (PHỦ ĐỊNH)
echo (true and false); // false (cú pháp PHP)
echo (true or false); // true
// ── Toán tử 3 ngôi ───────────────────────────
$diem = 7;
$ket_qua = ($diem >= 5) ? "Đậu" : "Trượt";
echo $ket_qua; // "Đậu"
// ── Null coalescing ──────────────────────────
$ten = null;
$hien_thi = $ten ?? "Khách"; // nếu null thì dùng "Khách"
echo $hien_thi; // "Khách"
✅ Đánh dấu đã học xong
Bài 7 Xử lý chuỗi
strings.gp
<gp>
$s = " Hello, GracePL World! ";
// Độ dài và cắt
echo strlen($s); // 26
echo trim($s); // "Hello, GracePL World!"
echo ltrim($s); // trim trái
echo rtrim($s); // trim phải
// Chuyển hoa/thường
echo strtoupper($s); // " HELLO, GRACEPL WORLD! "
echo strtolower($s); // " hello, gracepl world! "
// Tìm kiếm
$pos = strpos($s, "GracePL"); // vị trí đầu tiên (hoặc false)
if ($pos !== false) {
echo "Tìm thấy tại vị trí $pos";
}
echo str_contains($s, "World"); // true
echo str_starts_with($s, " Hello"); // true
echo str_ends_with($s, "World! "); // true
// Thay thế & cắt
echo str_replace("GracePL", "GP", $s); // thay "GracePL" bằng "GP"
echo substr($s, 9, 7); // "GracePL" (bắt đầu tại 9, lấy 7 ký tự)
echo substr($s, -8); // "World! " (từ cuối)
// Tách & ghép
$parts = explode(", ", "Alice,Bob,Charlie", 3); // tách theo dấu ","
foreach ($parts as $name) { echo $name . "\n"; }
$bienso = ["Hà Nội", "HCM", "Đà Nẵng"];
echo implode(" | ", $bienso); // "Hà Nội | HCM | Đà Nẵng"
// Escape HTML / xóa thẻ / Markdown / slug
echo htmlspecialchars("<script>"); // in ra <script>
echo htmlentities($s); echo html_decode("<"); // encode/decode HTML
echo addslashes("O'Brien"); // O\'Brien
echo strip_tags("<b>Hello</b>"); // Hello
echo markdown_to_html("**bold**"); // <strong>bold</strong>
echo slugify("Xin chào GracePL!", "vi"); // xin-chao-gracepl
// Định dạng
$ten = "Alice"; $diem = 9.5;
echo sprintf("%-10s: %.1f điểm", $ten, $diem); // "Alice : 9.5 điểm"
// Regex
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', "2026-03-07")) {
echo "Định dạng ngày hợp lệ!";
}
$email_cleaned = preg_replace('/\s+/', '', "user @example. com");
echo $email_cleaned; // "user@example.com"
✅ Đánh dấu đã học xong
Bài 8 Điều kiện if / else / match
conditions.gp
<gp>
$diem = 75;
// ── if / elseif / else ──────────────────────
if ($diem >= 90) {
echo "Xuất sắc";
} elseif ($diem >= 80) {
echo "Giỏi";
} elseif ($diem >= 65) {
echo "Khá";
} elseif ($diem >= 50) {
echo "Trung bình";
} else {
echo "Yếu — cần ôn thêm!";
}
// ── switch ──────────────────────────────────
$ngay = "Monday";
switch ($ngay) {
case "Monday":
case "Tuesday":
echo "Đầu tuần";
break;
case "Wednesday":
echo "Giữa tuần";
break;
case "Saturday":
case "Sunday":
echo "Cuối tuần 🎉";
break;
default:
echo "Ngày bình thường";
}
// ── match expression (như switch nhưng gọn hơn) ──
$status_code = 404;
$message = match($status_code) {
200, 201 => "Thành công",
400 => "Yêu cầu không hợp lệ",
401, 403 => "Không có quyền",
404 => "Không tìm thấy",
500 => "Lỗi máy chủ",
default => "Lỗi không xác định ($status_code)"
};
echo $message; // "Không tìm thấy"
✅ Đánh dấu đã học xong
Bài 9 Vòng lặp
loops.gp
<gp>
// ── for ────────────────────────────────────
for ($i = 1; $i <= 5; $i++) {
echo "Lần $i\n";
}
// ── while ───────────────────────────────────
$n = 10;
while ($n > 0) {
echo $n . " ";
$n -= 3;
}
echo "\n"; // 10 7 4 1
// ── do...while — chạy ít nhất 1 lần ──────────
$count = 0;
do {
echo "Thực hiện lần " . ($count + 1) . "\n";
$count++;
} while ($count < 3);
// ── foreach — duyệt mảng ────────────────────
$fruits = ["Táo", "Cam", "Chuối", "Xoài"];
foreach ($fruits as $index => $fruit) {
echo "$index: $fruit\n";
}
// ── foreach với map ─────────────────────────
$scores = ["Alice" => 95, "Bob" => 80, "Charlie" => 75];
foreach ($scores as $name => $score) {
echo "$name: $score điểm\n";
}
// ── break & continue ───────────────────────
for ($i = 1; $i <= 10; $i++) {
if ($i == 5) continue; // bỏ qua 5
if ($i == 8) break; // dừng tại 8
echo $i . " "; // 1 2 3 4 6 7
}
✅ Đánh dấu đã học xong
Bài 10 Hàm (Function)
functions.gp
<gp>
// ── Hàm cơ bản ──────────────────────────────
function chao($ten) {
return "Xin chào, $ten!";
}
echo chao("Alice"); // "Xin chào, Alice!"
// ── Giá trị mặc định ─────────────────────────
function tinhDien($kwh, $gia_don = 1800) {
return $kwh * $gia_don;
}
echo tinhDien(100); // 180000
echo tinhDien(100, 2000); // 200000
// ── Nhiều tham số, toán tử splat ─────────────
function tong(...$nums) {
$s = 0;
foreach ($nums as $n) $s += $n;
return $s;
}
echo tong(1, 2, 3, 4, 5); // 15
// ── Closure (hàm ẩn danh) ──────────────────
$nhan_doi = function($x) { return $x * 2; };
echo $nhan_doi(7); // 14
// ── Higher-order functions ───────────────────
$nums = [1, 2, 3, 4, 5, 6];
$chan = array_filter($nums, function($n) { return $n % 2 == 0; });
$binh_phuong = array_map(function($n) { return $n ** 2; }, $nums);
$tong_tat_ca = array_reduce($nums, function($carry, $n) { return $carry + $n; }, 0);
print_r($chan); // [2, 4, 6]
print_r($binh_phuong); // [1, 4, 9, 16, 25, 36]
echo $tong_tat_ca; // 21
// ── Trả về nhiều giá trị ─────────────────────
function chia_for($a, $b) {
if ($b == 0) return [null, "Không thể chia cho 0"];
return [$a / $b, null];
}
[$ket_qua, $loi] = chia_for(10, 3);
if ($loi !== null) { echo "Lỗi: $loi"; }
else { echo "Kết quả: $ket_qua"; }
✅ Đánh dấu đã học xong
Bài 11 Mảng (Array) & Map
arrays.gp
<gp>
// ── Mảng số (indexed) ───────────────────────
$fruits = ["Táo", "Cam", "Xoài"];
echo $fruits[0]; // "Táo"
echo count($fruits); // 3
$fruits[] = "Chuối"; // thêm vào cuối
array_push($fruits, "Dâu"); // tương tự
echo array_pop($fruits); // "Dâu" (xóa & trả về cuối)
echo array_shift($fruits); // "Táo" (xóa & trả về đầu)
array_unshift($fruits, "Nho"); // thêm vào đầu
// ── Sắp xếp ─────────────────────────────────
$nums = [3, 1, 4, 1, 5, 9, 2, 6];
sort($nums); // sắp xếp tăng dần
rsort($nums); // sắp xếp giảm dần
echo implode(", ", $nums);
// ── Tìm kiếm ────────────────────────────────
echo in_array("Cam", $fruits); // true
echo array_search("Cam", $fruits); // index của "Cam"
// ── Cắt & nối ──────────────────────────────
$a = [1, 2, 3]; $b = [4, 5, 6];
$c = array_merge($a, $b); // [1,2,3,4,5,6]
$slice = array_slice($c, 1, 3); // [2,3,4]
// ── Mảng kết hợp (Map / Associative Array) ──
$user = [
"ten" => "Alice",
"tuoi" => 25,
"email" => "alice@example.com"
];
echo $user["ten"]; // "Alice"
$user["city"] = "Hà Nội"; // thêm key mới
// Duyệt
foreach ($user as $key => $val) {
echo "$key: $val\n";
}
// Kiểm tra key tồn tại
if (array_key_exists("email", $user)) {
echo "Email: " . $user["email"];
}
echo isset($user["phone"]); // false
// ── array_keys & array_values ──────────────
$keys = array_keys($user); // ["ten","tuoi","email","city"]
$vals = array_values($user); // ["Alice",25,"alice@example.com","Hà Nội"]
⚠️ array_push trả về số, không phải mảng:
array_push($arr, $val) sửa $arr trực tiếp và trả về số phần tử mới. Không viết $arr = array_push($arr, $val) — câu đó sẽ ghi đè $arr bằng một số nguyên.
✅ Đánh dấu đã học xong
Bài 12 JSON — Trao đổi dữ liệu
json.gp
<gp>
// ── Encode: GracePL → JSON string ───────────
$data = [
"ten" => "Alice",
"tuoi" => 25,
"skills" => ["Go", "GracePL", "Docker"],
"active" => true
];
$json = json_encode($data);
echo $json;
// {"ten":"Alice","tuoi":25,"skills":["Go","GracePL","Docker"],"active":true}
// JSON đẹp (pretty-print)
$pretty = json_encode($data, JSON_PRETTY_PRINT);
echo $pretty;
// ── Decode: JSON string → GracePL ───────────
$json_str = '{"name":"Bob","age":30,"scores":[85,92,78]}';
$obj = json_decode($json_str);
echo $obj["name"]; // "Bob"
echo $obj["scores"][1]; // 92
// ── Đọc/ghi file JSON ───────────────────────
// Ghi
file_put_contents("config.json", json_encode($data, JSON_PRETTY_PRINT));
// Đọc
$config = json_decode(file_get_contents("config.json"));
echo $config["ten"]; // "Alice"
// ── Decode array of objects ──────────────────
$list_json = '[{"id":1,"name":"Apple"},{"id":2,"name":"Banana"}]';
$items = json_decode($list_json);
foreach ($items as $item) {
echo $item["id"] . ": " . $item["name"] . "\n";
}
✅ Đánh dấu đã học xong
Bài 13 Class & OOP (Lập trình hướng đối tượng)
oop.gp
<gp>
class User {
// Thuộc tính (properties)
public $ten;
public $email;
private $mat_khau; // chỉ truy cập trong class
public static $so_nguoi_dung = 0; // class variable
// Constructor — gọi khi tạo đối tượng
public function __construct($ten, $email, $mat_khau) {
$this->ten = $ten;
$this->email = $email;
$this->mat_khau = password_hash($mat_khau);
self::$so_nguoi_dung++;
}
// Phương thức thông thường
public function xin_chao() {
return "Xin chào, tôi là {$this->ten}!";
}
// Getter (truy cập biến private)
public function get_email() {
return $this->email;
}
// Verify password
public function kiem_tra_mat_khau($mat_khau_nhap) {
return password_verify($mat_khau_nhap, $this->mat_khau);
}
// Static method — gọi qua tên class không cần tạo đối tượng
public static function dem_nguoi_dung() {
return self::$so_nguoi_dung;
}
// __toString — hiển thị đối tượng dưới dạng chuỗi
public function __toString() {
return "User({$this->ten}, {$this->email})";
}
}
// Tạo đối tượng
$alice = new User("Alice", "alice@example.com", "password123");
$bob = new User("Bob", "bob@example.com", "secret456");
echo $alice->xin_chao(); // "Xin chào, tôi là Alice!"
echo $alice->kiem_tra_mat_khau("password123"); // true
echo User::dem_nguoi_dung(); // 2
echo $alice; // "User(Alice, alice@example.com)"
✅ Đánh dấu đã học xong
Bài 14 Kế thừa & Interface
inheritance.gp
<gp>
// ── Class cha ───────────────────────────────
class Animal {
public $ten;
public function __construct($ten) { $this->ten = $ten; }
public function mo_ta() { return "Tôi là {$this->ten}"; }
public function keu() { return "..."; }
}
// ── Kế thừa với extends ──────────────────────
class Dog extends Animal {
public $giong;
public function __construct($ten, $giong) {
parent::__construct($ten); // gọi constructor cha
$this->giong = $giong;
}
public function keu() { return "Gâu! Gâu!"; } // override
public function mo_ta() {
return parent::mo_ta() . " ({$this->giong})"; // mở rộng
}
}
class Cat extends Animal {
public function keu() { return "Meo! Meo!"; }
}
$dog = new Dog("Buddy", "Golden Retriever");
$cat = new Cat("Mimi");
echo $dog->mo_ta(); // "Tôi là Buddy (Golden Retriever)"
echo $dog->keu(); // "Gâu! Gâu!"
echo $cat->keu(); // "Meo! Meo!"
// Polymorphism — xử lý các loại khác nhau qua interface chung
$animals = [$dog, $cat, new Dog("Rex", "German Shepherd")];
foreach ($animals as $a) {
echo $a->ten . ": " . $a->keu() . "\n";
}
✅ Đánh dấu đã học xong
Bài 15 Interface & Trait
interface.gp
<gp>
// Interface: định nghĩa "hợp đồng" — class phải implement
interface Serializable {
public function serialize(): string;
public function to_array(): array;
}
interface Loggable {
public function log_info(): string;
}
// Implement nhiều interface với implements
class UserProfile implements Serializable, Loggable {
public $id;
public $ten;
public $email;
public function __construct($id, $ten, $email) {
$this->id = $id; $this->ten = $ten; $this->email = $email;
}
public function serialize(): string {
return json_encode($this->to_array());
}
public function to_array(): array {
return ["id" => $this->id, "ten" => $this->ten, "email" => $this->email];
}
public function log_info(): string {
return "[User#{$this->id}] {$this->ten} <{$this->email}>";
}
}
$profile = new UserProfile(1, "Alice", "alice@example.com");
echo $profile->serialize(); // JSON string
echo $profile->log_info(); // "[User#1] Alice "
// Trait: tái sử dụng code không qua kế thừa
trait Timestamps {
public $created_at;
public $updated_at;
public function touch() {
$this->updated_at = date("Y-m-d H:i:s");
}
public function just_created() {
$this->created_at = date("Y-m-d H:i:s");
$this->updated_at = $this->created_at;
}
}
class Post {
use Timestamps;
public $title;
public function __construct($title) {
$this->title = $title;
$this->just_created();
}
}
$post = new Post("Bài viết đầu tiên");
echo $post->created_at; // ngày tạo
✅ Đánh dấu đã học xong
Bài 16 HTTP Server đầu tiên
🎯 Mục tiêu: Tạo web server chạy được, xử lý requests và trả về responses.
Server đơn giản nhất
server.gp
<gp>
// Đăng ký route "/" — xử lý mọi request tới trang chủ
http_handle("/", function($req) {
// $req chứa: method, path, headers, query, body, params, cookies
$method = $req["method"]; // "GET", "POST", ...
$path = $req["path"]; // "/", "/about", ...
// Trả về chuỗi HTML
return "<h1>Xin chào từ GracePL! 🚀</h1><p>Method: $method</p>";
});
http_handle("/about", function($req) {
return "<h1>Về chúng tôi</h1><p>GracePL v3.0</p>";
});
// Khởi động server trên port 8080
echo "Server đang chạy tại http://localhost:8080\n";
http_serve(":8080");
terminal
gracepl run server.gp
# Mở trình duyệt: http://localhost:8080
Cấu trúc đối tượng $req
gracepl
<gp>
http_handle("/debug", function($req) {
// Xem toàn bộ request
$method = $req["method"]; // "GET"
$path = $req["path"]; // "/debug"
$query = $req["query"]; // ["key" => "value"] từ ?key=value
$headers = $req["headers"]; // map headers
$body = $req["body"]; // request body (string). Form/multipart → $req["files"][fieldName] = [{name, size, temp_path, content_type}]
$ip = $req["remote_addr"]; // "127.0.0.1:56789"
$cookies = $req["cookies"]; // map cookies
// Redirect: http_redirect($url); http_redirect_back([$fallback]) — về Referer hoặc fallback (mặc định /)
// Session flash (one-time message): session_flash("key", "value"); $msg = session_flash_get("key");
// Query string: GET /debug?name=Alice&page=2
$name = $query["name"] ?? "Khách";
$page = intval($query["page"] ?? 1);
return "<p>Xin chào $name, trang $page</p>";
});
http_serve(":8080");
✅ Đánh dấu đã học xong
Bài 17 Route Parameters & Định tuyến nâng cao
routing.gp
<gp>
// ── Route parameters (:id, :slug...) ───────
http_handle("/users/:id", function($req) {
$id = $req["params"]["id"]; // lấy từ URL
return "<p>User ID: $id</p>";
});
http_handle("/posts/:year/:slug", function($req) {
$year = $req["params"]["year"];
$slug = $req["params"]["slug"];
return "<p>Bài viết $slug năm $year</p>";
});
// ── Method-specific routes ───────────────────
http_route_get("/api/users", function($req) {
return json_encode(["users" => []]); // danh sách
});
http_route_post("/api/users", function($req) {
$body = json_decode($req["body"]);
// Tạo user mới
return json_encode(["created" => true, "name" => $body["name"]]);
});
http_route_put("/api/users/:id", function($req) {
$id = $req["params"]["id"];
$body = json_decode($req["body"]);
return json_encode(["updated" => $id]);
});
http_route_delete("/api/users/:id", function($req) {
$id = $req["params"]["id"];
return json_encode(["deleted" => $id]);
});
// ── Static files ─────────────────────────────
http_static("/static/", "public/"); // phục vụ file từ thư mục public/
// ── Middleware ───────────────────────────────
http_request_id(); // mỗi request có ID duy nhất
http_gzip(); // nén response
echo "API server: http://localhost:8080\n";
http_serve(":8080");
⚠️ Route wildcards không hoạt động:
/* là path literal, không phải wildcard. Chỉ dùng :param hoặc {param} cho segment động./news/:slug ✅ — /news/* ❌
⚠️ Kiểm tra key query string:
$req["query"]["x"] != false không đáng tin cậy vì null != false trả về true trong GracePL. Dùng isset($req["query"]["x"]).
✅ Đánh dấu đã học xong
Bài 18 Xây dựng REST API hoàn chỉnh
api.gp — REST API mẫu
<gp>
// Dữ liệu in-memory (thực tế dùng database)
$todos = [
["id" => 1, "title" => "Học GracePL", "done" => false],
["id" => 2, "title" => "Xây dựng API", "done" => true],
];
$next_id = 3;
// GET /api/todos — lấy danh sách
http_route_get("/api/todos", function($req) use (&$todos) {
http_json($todos);
});
// GET /api/todos/:id — lấy một item
http_route_get("/api/todos/:id", function($req) use (&$todos) {
$id = intval($req["params"]["id"]);
foreach ($todos as $todo) {
if ($todo["id"] == $id) {
http_json($todo);
return;
}
}
http_json(["error" => "Không tìm thấy"], 404);
});
// POST /api/todos — tạo mới
http_route_post("/api/todos", function($req) use (&$todos, &$next_id) {
$body = json_decode($req["body"]);
if (empty($body["title"])) {
http_json(["error" => "title là bắt buộc"], 400);
return;
}
$new_todo = ["id" => $next_id++, "title" => $body["title"], "done" => false];
$todos[] = $new_todo;
http_json($new_todo, 201);
});
// PUT /api/todos/:id — cập nhật
http_route_put("/api/todos/:id", function($req) use (&$todos) {
$id = intval($req["params"]["id"]);
$body = json_decode($req["body"]);
foreach ($todos as &$todo) {
if ($todo["id"] == $id) {
if (isset($body["title"])) $todo["title"] = $body["title"];
if (isset($body["done"])) $todo["done"] = (bool)$body["done"];
http_json($todo);
return;
}
}
http_json(["error" => "Không tìm thấy"], 404);
});
// DELETE /api/todos/:id
http_route_delete("/api/todos/:id", function($req) use (&$todos) {
$id = intval($req["params"]["id"]);
$todos = array_filter($todos, function($t) use ($id) { return $t["id"] != $id; });
$todos = array_values($todos);
http_json(["deleted" => $id]);
});
http_cors(); // bật CORS cho frontend
echo "Todo API: http://localhost:8080\n";
http_serve(":8080");
Test bằng curl
bash
# Lấy danh sách
curl http://localhost:8080/api/todos
# Tạo mới
curl -X POST http://localhost:8080/api/todos \
-H "Content-Type: application/json" \
-d '{"title":"Học REST API"}'
# Cập nhật
curl -X PUT http://localhost:8080/api/todos/1 \
-H "Content-Type: application/json" \
-d '{"done":true}'
# Xóa
curl -X DELETE http://localhost:8080/api/todos/1
✅ Đánh dấu đã học xong
Bài 19 Render HTML Template
templates/index.html
<!DOCTYPE html>
<html lang="vi">
<head><title>{{page_title}} — MyApp</title></head>
<body>
<h1>Xin chào, {{username}}!</h1>
{{if is_admin}}
<div class="admin-banner">Bạn đang đăng nhập với quyền Admin</div>
{{endif}}
<!-- {{if}} hỗ trợ lồng nhau. Filter: {{key | slice:start:end}}, {{key | upper}}/lower, {{key | default:"x"}}, {{key | length}} -->
<h2>Danh sách bài viết</h2>
{{if posts}}
<ul>
{{foreach posts as post}}
<li>
<a href="/posts/{{post.id}}">{{post.title}}</a>
<span>{{post.date}}</span>
</li>
{{end}}
</ul>
{{else}}
<p>Chưa có bài viết nào.</p>
{{endif}}
{{include "templates/footer.html"}}
</body>
</html>
server.gp
<gp>
http_handle("/", function($req) {
$posts = [
["id" => 1, "title" => "Giới thiệu GracePL", "date" => "2026-01-15"],
["id" => 2, "title" => "Xây dựng REST API", "date" => "2026-02-01"],
["id" => 3, "title" => "Deploy lên production","date" => "2026-03-07"],
];
$html = render("templates/index.html", [
"page_title" => "Trang chủ",
"username" => "Alice",
"is_admin" => true,
"posts" => $posts,
]);
// Trả về HTML với đúng Content-Type
http_set_header("Content-Type", "text/html; charset=utf-8");
return $html;
});
http_serve(":8080");
💡 Bảo mật XSS: Biến
{{ten}} tự động escape HTML — < thành <. Nếu cần xuất HTML thô (trusted), dùng {{ten | raw}}.
⚠️ Biến chứa HTML markup phải dùng
| raw: Khi bạn render nhiều card/item thành chuỗi HTML rồi truyền vào template cha, biến đó phải dùng | raw:{{cards}} → hiển thị text thô <div>...</div> ❌{{cards | raw}} → render HTML đúng cách ✅
✅ Đánh dấu đã học xong
Bài 20 CRUD với Database
🎯 Mục tiêu: Kết nối database và thực hiện Create, Read, Update, Delete.
db.gp
<gp>
// ── Kết nối ──────────────────────────────────
// SQLite (không cần cài server — tốt cho học)
$db = db_connect("sqlite://./data.db");
// MySQL / MariaDB
// $db = db_connect("mysql://user:pass@localhost:3306/mydb");
// PostgreSQL
// $db = db_connect("postgres://user:pass@localhost:5432/mydb");
// ── Tạo bảng ────────────────────────────────
db_exec($db, "CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ten TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
tuoi INTEGER,
ngay_tao TEXT DEFAULT CURRENT_TIMESTAMP
)");
// ── CREATE — thêm dữ liệu ───────────────────
db_exec("INSERT INTO users (ten, email, tuoi) VALUES (?, ?, ?)",
["Alice", "alice@example.com", 25]);
// ── READ — đọc dữ liệu ──────────────────────
$users = db_query("SELECT * FROM users ORDER BY id");
foreach ($users as $u) {
echo "{$u['id']}: {$u['ten']} ({$u['email']})\n";
}
// Một bản ghi
$user = db_query_row("SELECT * FROM users WHERE id = ?", [1]);
if ($user) echo "Tìm thấy: " . $user["ten"];
// Với điều kiện
$adults = db_query("SELECT * FROM users WHERE tuoi >= ?", [18]);
// ── UPDATE — cập nhật ───────────────────────
db_exec("UPDATE users SET tuoi = ?, email = ? WHERE id = ?",
[26, "alice_new@example.com", 1]);
// ── DELETE — xóa ────────────────────────────
db_exec("DELETE FROM users WHERE id = ?", [1]);
// ── Transaction — đảm bảo tính toàn vẹn ─────
$tx = db_begin();
try {
db_tx_exec($tx, "INSERT INTO users (ten, email, tuoi) VALUES (?, ?, ?)", ["Bob", "bob@example.com", 30]);
db_tx_exec($tx, "INSERT INTO users (ten, email, tuoi) VALUES (?, ?, ?)", ["Carol", "carol@example.com", 28]);
db_commit($tx);
echo "Transaction thành công!";
} catch ($e) {
db_rollback($tx);
echo "Transaction thất bại: " . $e->getMessage();
}
db_close();
✅ Đánh dấu đã học xong
Bài 21 Form Validation
validation.gp
<gp>
http_handle("/register", function($req) {
if ($req["method"] == "GET") {
// Hiện form đăng ký
return render("templates/register.html", ["errors" => []]);
}
// POST — xử lý form
$body = json_decode($req["body"]);
// Validate với rule engine tích hợp
$result = validate($body, [
"ten" => "required|string|min:2|max:50",
"email" => "required|email",
"mat_khau" => "required|string|min:8",
"tuoi" => "required|numeric|min:13|max:120",
"vai_tro" => "required|in:user,admin"
]);
if (!$result["valid"]) {
http_json(["errors" => $result["errors"]], 422);
return;
}
// Dữ liệu hợp lệ — lưu vào DB
db_connect("sqlite://./app.db");
db_exec("INSERT INTO users (ten, email, mat_khau, tuoi) VALUES (?, ?, ?, ?)",
[$body["ten"], $body["email"], password_hash($body["mat_khau"]), intval($body["tuoi"])]);
db_close();
http_json(["success" => true, "user_id" => $id], 201);
});
http_serve(":8080");
✅ Đánh dấu đã học xong
Bài 22 Xác thực với JWT
auth.gp
<gp>
env_load(".env");
$JWT_SECRET = env("JWT_SECRET", "change-this-secret-in-production");
$db = db_connect(env("DB_URL", "sqlite://./app.db"));
// ── Login ─────────────────────────────────────
http_route_post("/auth/login", function($req) use ($db, $JWT_SECRET) {
$body = json_decode($req["body"]);
$email = $body["email"] ?? "";
$pass = $body["password"] ?? "";
if (empty($email) || empty($pass)) {
http_json(["error" => "Thiếu email hoặc mật khẩu"], 400);
return;
}
$user = db_query_row($db, "SELECT * FROM users WHERE email = ?", [$email]);
if (!$user || !password_verify($pass, $user["mat_khau"])) {
http_json(["error" => "Email hoặc mật khẩu không đúng"], 401);
return;
}
// Tạo JWT token — hết hạn sau 24 giờ
$token = jwt_encode([
"user_id" => $user["id"],
"email" => $user["email"],
"role" => $user["vai_tro"] ?? "user"
], $JWT_SECRET, 86400);
http_json(["token" => $token, "user" => ["id" => $user["id"], "ten" => $user["ten"]]]);
});
// ── Middleware xác thực ──────────────────────
function require_auth($req, $secret) {
$auth = $req["headers"]["Authorization"] ?? $req["headers"]["authorization"] ?? "";
if (!str_starts_with($auth, "Bearer ")) {
http_json(["error" => "Cần xác thực"], 401);
return null;
}
$token = substr($auth, 7);
$claims = jwt_decode($token, $secret);
if (!$claims) {
http_json(["error" => "Token không hợp lệ hoặc đã hết hạn"], 401);
return null;
}
return $claims; // trả về payload của token
}
// ── Protected route ──────────────────────────
http_route_get("/api/profile", function($req) use ($db, $JWT_SECRET) {
$claims = require_auth($req, $JWT_SECRET);
if (!$claims) return; // đã trả error trong require_auth
$user = db_query_row($db, "SELECT id,ten,email FROM users WHERE id = ?", [$claims["user_id"]]);
http_json($user);
});
echo "Auth server: http://localhost:8080\n";
http_serve(":8080");
✅ Đánh dấu đã học xong
Bài 23 Xử lý File & Upload
files.gp
<gp>
// ── Đọc / ghi file ──────────────────────────
$content = file_get_contents("data.txt");
file_put_contents("output.txt", "Hello from GracePL!\n");
file_put_contents("log.txt", date("Y-m-d H:i:s") . " - Event\n", FILE_APPEND);
// ── Kiểm tra file / thư mục ─────────────────
if (file_exists("config.json")) {
$config = json_decode(file_get_contents("config.json"));
}
echo is_file("script.gp"); // true
echo is_dir("templates/"); // true
// ── Làm việc với thư mục ────────────────────
mkdir("uploads/avatars/", 0755, true); // tạo đệ quy
$files = scandir("uploads/"); // liệt kê file
$phpFiles = glob("*.gp"); // wildcard
$subDirs = glob("src/*/", GLOB_ONLYDIR); // chỉ thư mục
// ── Upload file qua HTTP (multipart form) ────
// $req["files"][fieldName] = array of {name, size, temp_path, content_type}
http_route_post("/upload", function($req) {
$files = $req["files"]["file"] ?? []; // mảng file (có thể nhiều file 1 field)
$file = $files[0] ?? null;
if (!$file) {
http_json(["error" => "Không tìm thấy file"], 400);
return;
}
// Kiểm tra mime type (bảo mật)
$allowed = ["image/jpeg", "image/png", "image/gif", "application/pdf"];
if (!in_array($file["content_type"], $allowed)) {
http_json(["error" => "Loại file không được phép"], 415);
return;
}
// Kiểm tra kích thước (5MB max)
if ($file["size"] > 5 * 1024 * 1024) {
http_json(["error" => "File quá lớn (max 5MB)"], 413);
return;
}
// Tạo tên file ngẫu nhiên để tránh overwrite
$ext = substr($file["name"], strrpos($file["name"], "."));
$newname = "uploads/" . sha1($file["name"] . time()) . $ext;
// Lưu file (temp_path là đường dẫn file tạm)
file_write_bytes($newname, file_read_bytes($file["temp_path"]));
http_json(["url" => "/static/" . basename($newname), "size" => $file["size"]]);
});
http_static("/static/", "uploads/");
http_serve(":8080");
✅ Đánh dấu đã học xong
Bài 24 Concurrency — Goroutine & Channel
🎯 Mục tiêu: Chạy nhiều tác vụ song song — GracePL dùng model goroutine và channel giống Go.
concurrency.gp
<gp>
// ── go {} — chạy trong goroutine mới ─────────
$ch = chan_make(10); // channel với buffer 10
go {
sleep_ms(100); // giả lập xử lý
chan_send($ch, "Kết quả từ goroutine 1");
}
go {
sleep_ms(200);
chan_send($ch, "Kết quả từ goroutine 2");
}
// Nhận kết quả từ 2 goroutines
$r1 = chan_recv($ch);
$r2 = chan_recv($ch);
echo "$r1\n$r2\n";
// ── WaitGroup — chờ nhiều goroutines ─────────
$wg = wg_make();
$results = [];
for ($i = 1; $i <= 5; $i++) {
wg_add($wg, 1);
$n = $i;
go_run(function() use ($wg, $n, &$results) {
sleep_ms($n * 50);
$results[] = "Task $n xong";
wg_done($wg);
});
}
wg_wait($wg); // chờ tất cả xong
foreach ($results as $r) echo $r . "\n";
// ── Parallel HTTP requests ────────────────────
$urls = [
"https://jsonplaceholder.typicode.com/users/1",
"https://jsonplaceholder.typicode.com/users/2",
"https://jsonplaceholder.typicode.com/users/3",
];
$chan = chan_make(count($urls));
foreach ($urls as $url) {
go_run(function() use ($url, $chan) {
$data = json_decode(http_get($url));
chan_send($chan, $data["name"] ?? "unknown");
});
}
for ($i = 0; $i < count($urls); $i++) {
echo chan_recv($chan) . "\n";
}
✅ Đánh dấu đã học xong
Bài 25 Xử lý lỗi & Exception
errors.gp
<gp>
// ── try / catch / finally ────────────────────
function chia($a, $b) {
if ($b == 0) {
throw new Exception("Không thể chia cho 0");
}
return $a / $b;
}
try {
$result = chia(10, 0);
echo $result;
} catch (Exception $e) {
echo "Lỗi: " . $e->message . "\n";
echo "Tại: " . $e->file . ":" . $e->line . "\n";
} finally {
echo "Khối finally luôn chạy\n";
}
// ── Custom Exception ─────────────────────────
class ValidationException extends Exception {
public $errors;
public function __construct($errors) {
parent::__construct("Dữ liệu không hợp lệ");
$this->errors = $errors;
}
}
function createUser($data) {
$errors = [];
if (empty($data["ten"])) $errors[] = "Thiếu tên";
if (empty($data["email"])) $errors[] = "Thiếu email";
if (!empty($errors)) throw new ValidationException($errors);
// ...tạo user
}
try {
createUser(["ten" => ""]);
} catch (ValidationException $e) {
echo "Lỗi validation:\n";
foreach ($e->errors as $err) echo " - $err\n";
} catch (Exception $e) {
echo "Lỗi chung: " . $e->message;
}
// ── is_error() — kiểm tra giá trị lỗi ────────
$result = db_connect("invalid://bad-url");
if (is_error($result)) {
echo "Kết nối thất bại!";
log_error("DB connection failed", ["url" => "invalid://bad-url"]);
}
✅ Đánh dấu đã học xong
Bài 26 🏗️ Dự án thực tế: Blog API + Frontend
🎯 Mục tiêu: Tổng hợp tất cả kiến thức để xây dựng một ứng dụng blog hoàn chỉnh từ đầu đến cuối.
Cấu trúc dự án
cây thư mục
my-blog/
├── server.gp # Entry point
├── .env # Cấu hình (DB, JWT_SECRET...)
├── db/
│ └── migrations.gp # Tạo bảng
├── templates/
│ ├── base.html # Layout chung (header/footer)
│ ├── home.html # Trang chủ
│ ├── post.html # Chi tiết bài viết
│ └── admin.html # Quản lý (cần đăng nhập)
└── public/
├── style.css
└── app.js
server.gp — Entry point
server.gp
<gp>
env_load(".env");
$DB = db_connect(env("DB_URL", "sqlite://./blog.db"));
$KEY = env("JWT_SECRET", "my-secret-key");
$PORT = env("PORT", "8080");
// ── Init DB ──────────────────────────────────
db_exec($DB, "CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
slug TEXT UNIQUE,
content TEXT,
author TEXT,
published INTEGER DEFAULT 0,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)");
// ── Middleware ────────────────────────────────
http_request_id();
http_panic_recover();
http_security_headers();
http_gzip();
log_format("json");
http_static("/static/", "public/");
// ── Trang chủ (công khai) ────────────────────
http_route_get("/", function($req) use ($DB) {
$posts = db_query($DB, "SELECT id,title,slug,author,created_at FROM posts WHERE published=1 ORDER BY id DESC LIMIT 10");
return render("templates/home.html", ["posts" => $posts, "title" => "Blog GracePL"]);
});
// ── Chi tiết bài viết ────────────────────────
http_route_get("/posts/:slug", function($req) use ($DB) {
$slug = $req["params"]["slug"];
$post = db_query_row($DB, "SELECT * FROM posts WHERE slug=? AND published=1", [$slug]);
if (!$post) { http_status(404); return render("templates/404.html", []); }
return render("templates/post.html", ["post" => $post]);
});
// ── API — cần xác thực ───────────────────────
http_route_post("/api/posts", function($req) use ($DB, $KEY) {
$auth = $req["headers"]["Authorization"] ?? "";
$claims = jwt_decode(substr($auth, 7), $KEY);
if (!$claims || $claims["role"] !== "admin") {
http_json(["error" => "Không có quyền"], 403); return;
}
$b = json_decode($req["body"]);
$v = validate($b, ["title"=>"required|min:3", "content"=>"required|min:10"]);
if (!$v["valid"]) { http_json(["errors"=>$v["errors"]], 422); return; }
$slug = strtolower(str_replace(" ", "-", preg_replace('/[^\w\s-]/', '', $b["title"])));
db_exec("INSERT INTO posts (title,slug,content,author,published) VALUES (?,?,?,?,?)", [$b["title"],$slug,$b["content"],$claims["name"],1]);
http_json(["id"=>$id, "slug"=>$slug], 201);
});
// ── Auth ──────────────────────────────────────
http_route_post("/api/login", function($req) use ($KEY) {
$b = json_decode($req["body"]);
// Trong thực tế: kiểm tra DB
if ($b["email"] === env("ADMIN_EMAIL","admin@blog.com") &&
$b["password"] === env("ADMIN_PASS","admin123")) {
$token = jwt_encode(["name"=>"Admin","role"=>"admin"], $KEY, 86400);
http_json(["token" => $token]);
} else {
http_json(["error" => "Sai thông tin đăng nhập"], 401);
}
});
echo "Blog đang chạy: http://localhost:$PORT\n";
http_serve(":$PORT");
Chạy dự án
bash
cd my-blog
gracepl run server.gp
# Test tạo bài viết
TOKEN=$(curl -s -X POST http://localhost:8080/api/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@blog.com","password":"admin123"}' | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
curl -X POST http://localhost:8080/api/posts \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Bài viết đầu tiên","content":"Nội dung bài viết..."}'
# Xem trang chủ
open http://localhost:8080
🎉 Chúc mừng! Bạn đã hoàn thành toàn bộ tutorial GracePL! Bây giờ bạn có thể xây dựng các ứng dụng web thực tế. Hãy xem tài liệu đầy đủ để khám phá thêm các tính năng nâng cao.
✅ Đánh dấu đã học xong