PR ที่แก้ logic แค่บรรทัดเดียว แต่ diff ขึ้นมาห้าสิบบรรทัด เพราะ editor ของคนแก้จัด indent ใหม่ทั้งไฟล์ ใครเคยรีวิว PR แบบนี้คงรู้ว่าต้องไล่หาบรรทัดที่เปลี่ยนจริงนานแค่ไหน formatter แก้เรื่องนี้ด้วยการจัดโค้ดของทุกคนให้ออกมาหน้าตาเดียวกัน ฝั่ง PHP ในโปรเจกต์ Laravel ตัวที่ใช้คือ Laravel Pint
Pint เป็น formatter หนึ่งในสี่แบบของเครื่องมือตรวจโค้ด ถ้ายังสับสนว่า Pint ต่างจาก Rector หรือ lint ยังไง ลองอ่านโพสต์ที่แยกเครื่องมือแต่ละแบบก่อนได้ครับ
Pint คือ PHP CS Fixer ที่ตั้งค่ามาให้แล้ว
Pint สร้างอยู่บน PHP CS Fixer ซึ่งเป็นเครื่องมือจัดรูปแบบโค้ด PHP ที่มีมานาน และมีกฎให้เลือกหลายร้อยข้อ ปกติถ้าใช้ PHP CS Fixer ตรง ๆ เราต้องเขียนไฟล์ config เลือกกฎเอง Pint เลือกชุดกฎไว้ให้แล้ว (เอกสารเรียกว่า opinionated) โปรเจกต์ Laravel ใหม่ทุกตัวมี Pint ติดมาใน require-dev จึงรันได้เลย
vendor/bin/pint
โปรเจกต์เก่าที่ยังไม่มี ติดตั้งเพิ่มด้วย composer require laravel/pint --dev
คำสั่งที่ใช้บ่อย
| คำสั่ง | ทำอะไร |
|---|---|
vendor/bin/pint |
จัดทุกไฟล์ .php ในโปรเจกต์ ยกเว้น vendor |
vendor/bin/pint app/Models |
จัดเฉพาะโฟลเดอร์หรือไฟล์ที่ระบุ |
vendor/bin/pint --dirty |
จัดเฉพาะไฟล์ที่ยังไม่ได้ commit ตาม git |
vendor/bin/pint --diff=main |
จัดเฉพาะไฟล์ที่ต่างจาก branch main |
vendor/bin/pint --test |
ตรวจอย่างเดียว ไม่แก้ไฟล์ จบด้วย exit code ที่ไม่ใช่ 0 ถ้าเจอไฟล์ที่ยังไม่ผ่าน |
vendor/bin/pint --repair |
แก้ไฟล์ให้ แต่จบด้วย exit code ที่ไม่ใช่ 0 ถ้ามีไฟล์ที่ต้องแก้ |
vendor/bin/pint --parallel |
รันหลาย process พร้อมกัน (ยังเป็น experimental) |
ผลที่ได้หน้าตาแบบนี้ Pint บอกว่าแก้ไฟล์ไหน และแก้ด้วยกฎอะไร
vendor/bin/pint --dirty
✓
──────────────────────────────────────────────────────────────── Laravel
FIXED ................................... 1 file, 1 style issue fixed
✓ app/Services/PostTitle.php ordered_class_elements, class_attributes_separationตั้งแต่ Pint 1.30.0 ที่ออกเมื่อเดือนกรกฎาคม 2026 Pint จัดไฟล์ Blade ได้ด้วย สั่ง --blade หรือเปิดกฎ Pint/laravel_blade ใน pint.json กฎนี้ใช้ Prettier อยู่เบื้องหลัง เครื่องจึงต้องมี Node.js
Preset เป็นชุดกฎตั้งต้น
Pint มี preset ให้เลือก 5 แบบ คือ laravel, per, psr12, symfony และ empty ค่าเริ่มต้นคือ laravel ซึ่งเป็นรูปแบบเดียวกับโค้ดของตัว Laravel เอง
laravelรูปแบบของ Laravel เช่น ไม่มีช่องว่างรอบจุดต่อ string ('a'.$b) ใช้ quote เดี่ยว และnew Fooไม่ใส่วงเล็บperตาม PER Coding Style ของ PHP-FIG ซึ่งมาแทน PSR-12psr12ตาม PSR-12 สำหรับโปรเจกต์ที่ใช้มาตรฐานนี้อยู่แล้วsymfonyรูปแบบของ Symfonyemptyไม่มีกฎอะไรเลย ให้เราเลือกเองทั้งหมด
โปรเจกต์ Laravel ใช้ laravel ต่อไปดีที่สุด โค้ดของเราจะหน้าตาเหมือนโค้ดใน vendor/laravel และตัวอย่างในเอกสาร อ่านสลับไปมาได้โดยไม่สะดุด
เปิดกฎเพิ่มใน pint.json
Pint ไม่ต้องมีไฟล์ config ก็ได้ แต่ถ้าอยากเปลี่ยน preset หรือเปิดปิดกฎบางข้อ ให้สร้าง pint.json ที่ราก repo ชื่อกฎใช้ของ PHP CS Fixer ได้ทุกข้อ หาดูได้จาก PHP CS Fixer Configurator
เว็บนี้ใช้ preset laravel แล้วเปิดเพิ่มสามข้อ
{
"preset": "laravel",
"rules": {
"global_namespace_import": { "import_classes": true, "import_constants": false, "import_functions": false },
"fully_qualified_strict_types": { "import_symbols": true },
"ordered_class_elements": true
}
}
global_namespace_importย้ายคลาสของ PHP ที่เขียนเต็มไว้ในโค้ด อย่างnew \DateTimeImmutableไปเป็นuse DateTimeImmutable;ด้านบนไฟล์ ส่วนฟังก์ชันกับค่าคงที่อย่างcount()หรือPHP_EOLปล่อยไว้แบบเดิมfully_qualified_strict_typesทำแบบเดียวกันกับคลาสที่อยู่ใน namespace อย่าง\Illuminate\Support\Str::slug()ที่เผลอเขียนเต็มไว้กลางเมธอด จะกลายเป็นuse Illuminate\Support\Str;แล้วเหลือแค่Str::slug()ordered_class_elementsเรียงของในคลาสให้เป็นลำดับเดียวกันทุกไฟล์ ค่าเริ่มต้นของกฎนี้คือ trait, enum case, constant, property, constructor, magic method แล้วจึงเป็นเมธอด public, protected และ private
ลองดูคลาสที่เขียนสลับลำดับไว้ และมีชื่อคลาสเขียนเต็มอยู่ในโค้ด (ผลจากการรัน Pint จริงด้วย pint.json ข้างบน)
// เดิม
class Demo
{
public function handle(): int
{
$d = new \DateTimeImmutable();
$s = \Illuminate\Support\Str::slug('a b');
return count([$d, $s]);
}
private string $name = 'x';
const LIMIT = 3;
public function __construct() {}
}
// หลังรัน Pint
use DateTimeImmutable;
use Illuminate\Support\Str;
class Demo
{
const LIMIT = 3;
private string $name = 'x';
public function __construct() {}
public function handle(): int
{
$d = new DateTimeImmutable;
$s = Str::slug('a b');
return count([$d, $s]);
}
}
constant ขึ้นไปอยู่บนสุด ตามด้วย property และ constructor คลาสที่เขียนเต็มกลายเป็น use ด้านบน new DateTimeImmutable() เหลือ new DateTimeImmutable ตาม preset laravel และมีบรรทัดว่างก่อน return
ถ้าอยากให้ Pint ข้ามบางไฟล์ ใส่ไว้ใน exclude (โฟลเดอร์) notName (รูปแบบชื่อไฟล์) หรือ notPath (path ของไฟล์) ใน pint.json ได้
Pint ในเครื่องและใน pull request
ในเครื่อง เรารัน Pint หลัง Rector ทุกครั้ง เพราะโค้ดที่ Rector เขียนใหม่อาจยังไม่ตรงรูปแบบ
vendor/bin/rector process && vendor/bin/pint --dirty
editor ส่วนใหญ่ตั้งให้รัน Pint ทุกครั้งที่บันทึกไฟล์ได้ เช่น PhpStorm มี Laravel Pint อยู่ในหน้า Quality Tools ส่วน VS Code ก็มี extension ให้ติดตั้ง
ถ้าใช้ Claude Code ช่วยเขียนโค้ด ก็ตั้ง hook ให้รัน Pint กับไฟล์ที่ Claude เพิ่งแก้ได้ทุกครั้ง ตัวอย่าง hook ที่ใช้ได้ทั้ง Pint และ Prettier อยู่ในโพสต์ที่แยกเครื่องมือแต่ละแบบ
ใน pull request เว็บนี้รัน pint --test เฉพาะไฟล์ที่ PR แก้ ไฟล์ไหนยังไม่ได้จัด workflow จะไม่ผ่าน คนเปิด PR ก็รัน pint --dirty ในเครื่องแล้ว push ใหม่ เอกสารของ Pint มีอีกทางให้ด้วย คือให้ GitHub Actions รัน Pint แล้ว commit ไฟล์ที่จัดแล้วกลับเข้า branch เอง เราเลือกตรวจอย่างเดียวมากกว่า เพราะไม่อยากให้มี commit จาก bot แทรกเข้ามาใน branch ที่คนอื่นกำลังทำงานอยู่






