Có những hôm mình chỉ muốn chạy Pint một lần để xem một thư mục PHP có sạch không, nhưng lại phải cân nhắc ba lựa chọn: thêm tool vào require-dev, cài bằng Composer global, hoặc tải PHAR về rồi vài tuần sau quên luôn nó nằm ở đâu. Tool dùng năm phút mà khâu chuẩn bị nhìn như sắp dựng production, hơi mệt.
CPX giải quyết đúng chuyện đó. Nó được mô tả ngắn gọn là “npx dành cho Composer”: đưa tên package vào, CPX cài package trong vùng tách biệt rồi chạy binary của package. Mình biết đợt 2.0 qua bài giới thiệu trên Laravel News, nhưng thay vì dịch lại, mình dựng một lab riêng trên Windows, chạy thử với PHP 8.4 và Laravel Pint, đo cả lần đầu lẫn lần dùng cache xem có thật sự tiện không.
Mục lục [Hiển thị]
CPX 2.0 giải quyết vấn đề gì?
Composer vốn quản lý dependency theo từng project rất tốt. Nếu Pint, PHPStan hoặc PHPUnit là một phần bắt buộc của quy trình phát triển, thêm chúng vào require-dev vẫn là lựa chọn dễ tái hiện nhất. Vấn đề nằm ở những tool chỉ dùng một lần, hoặc khi mình muốn thử một phiên bản khác mà không sửa composer.json và composer.lock.
Cài tất cả bằng composer global require cũng dùng được, nhưng các tool global cùng chia sẻ một project Composer. Hai tool phụ thuộc vào các phiên bản khác nhau của Symfony Console, PHP Parser hoặc một thư viện chung có thể kéo nhau vào dependency conflict. CPX giữ từng package đã chạy trong vùng riêng, tách khỏi project và Composer global.
Theo repository chính thức của Laravel CPX, phiên bản 2.x còn ưu tiên binary đã có trong project. Ví dụ project đã khóa Laravel Pint ở một phiên bản cụ thể thì cpx pint sẽ dùng bản local đó; chỉ khi không tìm thấy bản phù hợp CPX mới cài một bản cô lập. Đây là chi tiết quan trọng, vì một CLI tiện tay không nên âm thầm bỏ qua version mà cả team đã pin.
| Nhu cầu | Cách phù hợp |
|---|---|
| Tool bắt buộc trong CI và mọi máy dev | Đưa vào require-dev và commit lock file |
| Tool dùng lâu dài trên toàn máy | Composer global, nếu dependency không xung đột |
| Tool dùng một lần hoặc cần nhiều version | CPX với package cô lập |
| Project đã có binary đúng version | CPX ưu tiên binary local |
Môi trường mình dùng để thử
Mình test ngày 31/07/2026 trên Windows, dùng PHP Herd và tách toàn bộ cache CPX vào thư mục lab. Mục tiêu là không cài thêm gì vào Composer global đang dùng, đồng thời nhìn rõ package nào thực sự được CPX quản lý.
| Thành phần | Phiên bản hoặc cấu hình |
|---|---|
| PHP | 8.4.23 |
| CPX | 2.0.0 |
| Package thử | laravel/pint |
| Hệ điều hành | Windows |
| Cache | Thư mục riêng trong lab |
Theo metadata của cpx/cpx trên Packagist, CPX 2.0 yêu cầu PHP 8.3 trở lên. Cách cài dành cho người dùng theo README là:
composer global require cpx/cpx:^2.0
composer global config bin-dir --absolute
Lệnh thứ hai cho biết thư mục binary global cần có trong PATH. Lab của mình dùng PHAR có sẵn trong package và đặt nó ra một thư mục sạch để dễ kiểm soát. Đây không phải hướng cài mình khuyên dùng hằng ngày; mình làm vậy để kết quả test không đụng trạng thái global của máy.
Thử cpx exec trước cho nhẹ nhàng
cpx exec chạy file PHP hoặc một đoạn code ngắn. Trong project Laravel, CPX có thể boot ứng dụng để dùng config, facade, container và autoloader. Mình chưa cần framework ở bài test đầu nên thêm --no-boot:
cpx exec --no-boot -r "echo PHP_VERSION;"
Output trên máy mình là:
8.4.23
Đoạn code chạy trong process PHP riêng, không chạy lẫn với dependency được bundle bên trong CPX. Chỗ này hữu ích khi cần đọc nhanh một file, kiểm tra API của PHP hoặc viết scratch script có autoload của project. Tuy nhiên, code truyền cho exec vẫn là code thật được thực thi trên máy; đừng lấy Gist hoặc đoạn lệnh chưa đọc rồi chạy chỉ vì CPX làm thao tác đó ngắn hơn.
Chạy Laravel Pint mà không thêm vào project
Tiếp theo mình buộc CPX bỏ qua binary local bằng --skip-local, rồi yêu cầu chạy laravel/pint trong một thư mục rỗng. Thư mục không có file PHP nên Pint chỉ xác nhận quá trình chạy thành công, không sửa code nào.
cpx --skip-local laravel/pint
Mình chạy cùng một lệnh hai lần và đo bằng Measure-Command. Kết quả:
| Lần chạy | Thời gian | Exit code | Output |
|---|---|---|---|
| Lần đầu | 2,71 giây | 0 | {"tool":"pint","result":"passed"} |
| Lần hai | 0,86 giây | 0 | {"tool":"pint","result":"passed"} |
Chênh lệch ở đây không phải benchmark tổng quát cho mọi máy. Lần đầu CPX phải chuẩn bị package trong cache lab; lần sau tái sử dụng bản đã có nên nhanh hơn. Tốc độ còn phụ thuộc mạng, package, Composer cache, antivirus và ổ đĩa. Điều mình muốn xác nhận chỉ là cơ chế cache hoạt động thật, và trên môi trường này lần chạy lại giảm từ 2,71 xuống 0,86 giây.

Lệnh quản lý package đã chạy cũng trả JSON đúng như tài liệu:
{
"success": true,
"errors": [],
"summary": {
"packages": [
{
"name": "laravel/pint",
"last_run": "2026-07-31 15:30:35"
}
]
}
}
Với một workflow tự động hoặc agent, JSON một dòng dễ parse hơn output trang trí dành cho terminal. CPX tự nhận diện non-interactive mode, và có thể ép kiểu output này bằng --json. Nó hợp với cách mình từng kiểm tra output có cấu trúc trong bài Laravel Boost: test xanh chưa đủ cho code AI: máy đọc được kết quả là tốt, nhưng vẫn cần người thiết kế điều kiện thành công và thất bại cho đúng.
Local-first và cache cô lập khác nhau ra sao?
Trong project có vendor/bin/pint, lệnh cpx pint ưu tiên binary của project. CPX đi ngược lên cây thư mục để tìm Composer project gần nhất và kiểm tra bin-dir. Nhờ vậy developer gõ một lệnh quen tay nhưng vẫn dùng version mà project đã khóa.
Nếu muốn bỏ qua bản local để thử một release khác, thêm --skip-local trước tên package:
cpx --skip-local laravel/pint
cpx --skip-local laravel/pint:^1.29
Package cô lập không được thêm vào composer.json của project. Nó cũng không chen vào project Composer global đang chứa chính CPX. Theo mình, đây mới là phần đáng tiền nhất: một lệnh có thể ưu tiên tính tái hiện của project, nhưng vẫn cho phép mở một “ngăn kéo” riêng khi cần thử tool tạm thời.

Dù vậy, cache cô lập không biến package thành đáng tin cậy. CPX vẫn tải và chạy code từ hệ sinh thái Composer. Trước khi dùng package lạ, mình vẫn kiểm tra tên vendor, repository, release, giấy phép và nội dung package. Quy trình này giống lúc mình thử một repository như Superfile trên terminal: tiện cài không thay thế việc biết mình đang chạy thứ gì.
Ba lỗi thật mình gặp trong lab
Process con không tìm thấy PHP
Mình gọi PHAR bằng đường dẫn đầy đủ tới php.exe, nên lệnh cpx --version chạy được. Nhưng CPX tạo process con bằng lệnh php; nếu thư mục PHP chưa nằm trong PATH, package con báo 'php' is not recognized. Sau khi thêm thư mục PHP Herd vào PATH, exec và Pint chạy bình thường.
Nếu gặp lỗi tương tự trên Windows, kiểm tra hai lệnh trước:
where.exe php
php -v
Chạy PHAR ngay trong source package làm nhiễu autoloader
Lần đầu mình dùng composer create-project cpx/cpx:2.0.0 rồi chạy builds/cpx ngay trong thư mục source. cpx exec phát hiện vendor/autoload.php gần đó, trong khi autoloader dev của bản dist còn tham chiếu file test không được đóng gói. Kết quả là lỗi thiếu tests/Support/FilesystemFakes.php.
Khi tách PHAR sang thư mục runtime sạch, lỗi biến mất. Đây là do cách mình dựng lab, không phải luồng cài chính thức bằng composer global require cpx/cpx. Mình vẫn ghi lại vì nó cho thấy khả năng tự động tìm autoloader rất tiện, nhưng lúc debug phải biết CPX đang boot project nào.
Option toàn cục có thể gây hiểu nhầm
Mình thử đặt --version sau tên laravel/pint, nhưng output nhận được vẫn là cpx v2.0.0. Symfony Console đã xử lý nó như option toàn cục của CPX thay vì chuyển xuống package. Khi cần chọn rõ binary hoặc truyền option dễ trùng, nên đọc cpx help, dùng tham số --bin theo command reference và kiểm tra output thay vì đoán.
CPX có thay thế require-dev không?
Không. Nếu CI bắt buộc chạy Pint, PHPStan hoặc Pest bằng một version cụ thể, dependency đó nên nằm trong project và lock file. Tài liệu Composer CLI cũng nhấn mạnh install dùng đúng phiên bản trong lock file khi file này tồn tại. Người khác clone repository phải tái hiện được cùng toolchain mà không dựa vào cache cá nhân.
CPX hợp hơn cho các tình huống sau:
- Thử một CLI Composer trước khi quyết định thêm vào project.
- Chạy tool phân tích hoặc chuyển đổi code một lần.
- So sánh hành vi của hai version mà không sửa lock file.
- Tạo alias ngắn cho package dài nhưng vẫn giữ từng package cô lập.
- Chạy scratch PHP với autoload hoặc boot Laravel khi cần.
- Cho automation đọc output JSON ổn định từ các command quản lý.
Tài liệu CPX còn mô tả các lệnh alias, aliases, installed, update và clean để quản lý những gì đã chạy. Nếu chuyển giữa nhiều project Laravel, việc local binary được ưu tiên sẽ giảm nguy cơ vô tình format code bằng một version global khác với team.
Với project realtime như bài bảng tỷ số Laravel Reverb, mình vẫn pin các tool kiểm thử chính trong project. CPX sẽ nằm ở lớp thử nghiệm: chạy nhanh một analyzer, một fixer hoặc một generator trước khi quyết định đưa nó vào quy trình chung.
Sau khi thử, mình có dùng CPX không?
Có, nhưng mình sẽ dùng đúng việc. CPX 2.0.0 chạy ổn trong lab Windows sau khi PHP có trong PATH. exec trả đúng PHP 8.4.23, Pint cô lập chạy exit 0, cache giúp lần hai giảm từ 2,71 xuống 0,86 giây, và command quản lý trả JSON sạch cho automation.
Điểm mình thích nhất không phải câu “npx cho Composer”, mà là quy tắc local-first. Nó tôn trọng binary project đã pin rồi mới dùng vùng cô lập. Điểm cần nhớ là CPX vẫn chạy code bên thứ ba, phụ thuộc vào PHP trong PATH, và không thay thế lock file cho tool bắt buộc của team.
Nếu anh em thường xuyên cài Pint, Rector hoặc PHPStan global chỉ để dùng một lần, CPX đáng để thử. Bắt đầu bằng một thư mục không quan trọng, chạy cpx installed --json để xem cache, rồi mới đưa vào workflow thật. Ngắn gọn là tiện, khá sạch, nhưng vẫn phải biết cartridge mình vừa cắm vào đang chứa code gì.