.
├── config/ Các tệp cấu hình
├── public/ Các tệp máy chủ web (DocumentRoot)
│ └── index.php Bộ điều khiển chính
├── src/ Mã nguồn PHP (Namespace App)
├── vendor/ Dành riêng cho composer
├── .gitignore Quy tắc git ignore
└── composer.json Các phụ thuộc dự án
Tự động tải (Autoloader) Để quản lý việc tải các lớp PHP trong dự án, chúng ta cần một Autoloader PSR-4.
PSR-4 định nghĩa tiêu chuẩn cho việc đặt tên và cấu trúc thư mục lớp, giúp dễ dàng tổ chức và tải tự động các lớp mà không cần lệnh require thủ công.
Tạo thư mục mới: src/
Thêm phần tự động tải sau vào file composer.json của bạn:
"autoload": {
"psr-4": {
"App\": "src/"
}
}
Với cấu hình này, ứng dụng của bạn sẽ tự động tải tất cả các lớp trong namespace App từ thư mục src/.
Đây là cách file composer.json hoàn chỉnh của bạn sẽ trông như thế nào:
{
"require": {
"nyholm/psr7": "^1",
"nyholm/psr7-server": "^1",
"slim/slim": "^4"
},
"autoload": {
"psr-4": {
"App\": "src/"
}
}
}
Chạy composer update để áp dụng thay đổi.
Cấu hình Hãy thiết lập các tệp cấu hình.
Thư mục dành cho tất cả các tệp cấu hình là: config/
Tạo thư mục config/ trong dự án của bạn.
Bên trong thư mục config/, tạo tệp tên là settings.php.
Sao chép và dán nội dung sau vào config/settings.php:
<?php
// Nên đặt thành 0 trong môi trường sản xuất
error_reporting(E_ALL);
// Nên đặt thành '0' trong môi trường sản xuất
ini_set('display_errors', '1');
// Cài đặt
$settings = [];
// ...
return $settings;
Tệp settings.php này đóng vai trò là tệp cấu hình chính, hợp nhất các cài đặt mặc định với các cài đặt dành riêng cho môi trường.
Bạn có thể điền thêm mảng $settings với các tùy chọn cấu hình cụ thể của dự án.
Container DI
Tiêm phụ thuộc là một kỹ thuật lập trình tách việc tạo đối tượng khỏi việc sử dụng chúng, nhằm mục tiêu tạo ra các chương trình lỏng lẻo gắn kết.
Không sử dụng tiêm phụ thuộc, các lớp thường mã hóa cứng các phụ thuộc của chúng.
Tiêm phụ thuộc tách các mối quan tâm bằng cách đảm bảo rằng các đối tượng nhận được các phụ thuộc cần thiết từ "bên ngoài". Tiêm phụ thuộc qua constructor là hình thức phổ biến nhất, nơi các phụ thuộc được truyền qua constructor.
Ví dụ:
final class Logger
{
public function log(string $message): void
{
echo "Logging: " . $message . "n";
}
}
final class UserCreator
{
private Logger $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
public function createUser(string $username): void
{
// ...
$this->logger->log("User created: " . $username);
}
}
// Sử dụng
$logger = new Logger();
$userCreator = new UserCreator($logger);
$userCreator->createUser('john.doe');
Việc xây dựng phụ thuộc một cách thủ công có thể trở nên phức tạp, trong khi một Container Tiêm Phụ Thuộc (DI Container) như PHP-DI có thể tự động hóa quá trình này, xem thêm Autowiring.
Để cài đặt PHP-DI, chạy lệnh:
composer require php-di/php-di
Định Nghĩa Container DI
Tạo một tệp mới cho các mục trong container DI: config/container.php và sao chép/dán nội dung sau:
<?php
use PsrContainerContainerInterface;
use SlimApp;
use SlimFactoryAppFactory;
return [
'settings' => function () {
return require __DIR__ . '/settings.php';
},
App::class => function (ContainerInterface $container) {
$app = AppFactory::createFromContainer($container);
// Đăng ký các route
(require __DIR__ . '/routes.php')($app);
// Đăng ký middleware
(require __DIR__ . '/middleware.php')($app);
return $app;
},
];
Lưu ý rằng chúng ta sử dụng container DI để xây dựng instance của Slim App và tải các cài đặt ứng dụng. Điều này cho phép chúng ta cấu hình các dịch vụ hạ tầng, như kết nối cơ sở dữ liệu, mailer, v.v., trong container DI. Tất cả các cài đặt chỉ được truyền dưới dạng một mảng đơn giản, vì vậy chúng ta không có định danh lớp đặc biệt (FQCN) ở đây.
Định danh App::class được cần để đảm bảo rằng chúng ta sử dụng cùng một đối tượng App trên toàn bộ ứng dụng và đảm bảo rằng đối tượng App sử dụng cùng một đối tượng container DI.
Hãy chắc chắn rằng bạn thêm các lệnh use cần thiết ở đầu tệp PHP để đảm bảo rằng các namespace đúng được định nghĩa.
Khởi Động (Bootstrap)
Quá trình khởi động ứng dụng chứa mã được thực thi khi ứng dụng (yêu cầu) bắt đầu.
Quy trình bootstrap bao gồm autoloader của Composer và sau đó tiếp tục xây dựng container DI, tạo ứng dụng và đăng ký các route + middleware.
Tạo tệp bootstrap: config/bootstrap.php và sao chép/dán nội dung sau:
<?php
use DIContainerBuilder;
use SlimApp;
require_once __DIR__ . '/../vendor/autoload.php';
// Xây dựng instance của container DI
$container = (new ContainerBuilder())
->addDefinitions(__DIR__ . '/container.php')
->build();
// Tạo instance của App
return $container->get(App::class);
Bộ Điều Khiển Chính (Front Controller)
Bộ điều khiển chính là điểm nhập của ứng dụng Slim của bạn và xử lý tất cả các yêu cầu bằng cách chuyển hướng các yêu cầu qua một đối tượng xử lý duy nhất.
Vì lý do bảo mật, bạn luôn nên đặt bộ điều khiển chính (index.php) vào thư mục public/. Bạn không bao giờ nên đặt bộ điều khiển chính trực tiếp vào thư mục gốc của dự án.
Tạo tệp bộ điều khiển chính: public/index.php và sao chép/dán nội dung sau:
<?php
(require __DIR__ . '/../config/bootstrap.php')->run();
Lưu ý: Thư mục public/ chỉ là DocumentRoot của máy chủ web của bạn, nhưng nó không bao giờ là phần của đường dẫn cơ bản và URL chính thức.
URL tốt:
https://www.example.com
https://www.example.com/users
https://www.example.com/my-app
https://www.example.com/my-app/users
URL không tốt:
https://www.example.com/public
https://www.example.com/public/users
https://www.example.com/public/index.php
https://www.example.com/my-app/public
https://www.example.com/my-app/public/users
Middleware
Middleware có thể được thực thi trước và sau khi ứng dụng Slim của bạn để đọc hoặc thao tác đối tượng yêu cầu và phản hồi.
Để chạy Slim, chúng ta cần thêm RoutingMiddleware và ErrorMiddleware của Slim.
- RoutingMiddleware sẽ định tuyến và phân phối các yêu cầu đến.
- ErrorMiddleware có thể bắt tất cả các ngoại lệ để hiển thị một trang lỗi đẹp mắt.
- BodyParsingMiddleware là tùy chọn, nhưng khuyến nghị nếu bạn làm việc với dữ liệu JSON hoặc form.
Tạo tệp: config/middleware.php để thiết lập các middleware toàn cục và sao chép/dán nội dung sau:
<?php
use SlimApp;
return function (App $app) {
// Phân tích json, dữ liệu form và xml
$app->addBodyParsingMiddleware();
// Thêm middleware định tuyến tích hợp của Slim
$app->addRoutingMiddleware();
// Xử lý ngoại lệ
$app->addErrorMiddleware(true, true, true);
};
Routes
Một route là một đường dẫn URL có thể ánh xạ đến một trình xử lý cụ thể. Trình xử lý đó có thể là một hàm đơn giản hoặc một lớp có thể gọi được (invokable class). Bên dưới, Slim sử dụng gói nikic/FastRoute, nhưng cũng thêm một số tính năng hay cho các nhóm định tuyến, tên và middleware, v.v.
Các route của ứng dụng sẽ được định nghĩa trong các tệp PHP thuần túy.
Tạo tệp: config/routes.php và sao chép/dán nội dung sau:
<?php
use SlimApp;
return function (App $app) {
// trống
};
Route đầu tiên
Mở tệp config/routes.php và chèn mã cho route đầu tiên:
<?php
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use SlimApp;
return function (App $app) {
$app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) {
$response->getBody()->write('Hello, World!');
return $response;
});
};
Mở một cửa sổ console mới và khởi động máy chủ phát triển tích hợp bằng lệnh sau:
php -S localhost:8080 -t public/
Terminal sẽ hiển thị:
[Sat Aug 6 10:38:50 2022] PHP 8.x Development Server (http://localhost:8080) started
Lưu ý: Máy chủ web này được thiết kế để hỗ trợ phát triển ứng dụng. Nó cũng có thể hữu ích cho mục đích kiểm thử hoặc cho các trình diễn ứng dụng được chạy trong các môi trường kiểm soát. Nó không được thiết kế để làm máy chủ web đầy đủ tính năng và không nên được sử dụng trong mạng công cộng.
- Tham số
-S localhost:8080 xác định tên máy chủ và cổng.
- Tham số
-t public/ chỉ định thư mục gốc tài liệu với tệp index.php.
Bây giờ, mở trang web của bạn, ví dụ: http://localhost:8080 và bạn sẽ thấy thông điệp: Hello, World!
Để đơn giản hóa bước này, bạn có thể thêm một lệnh script start và khóa process-timeout vào tệp composer.json của mình:
"config": {
"process-timeout": 0
},
"scripts": {
"start": "php -S localhost:8080 -t public/"
}
Để dừng máy chủ web tích hợp, nhấn: Ctrl+C.
Để khởi động lại máy chủ web tích hợp, bạn có thể sử dụng lệnh:
composer start
Controller Hành Động Đơn (Single Action Controller)
Slim cung cấp một số phương pháp để thêm logic controller trực tiếp trong callback route. Đối tượng yêu cầu/phản hồi (và tùy chọn là các đối số route) được Slim truyền cho một hàm callback như sau:
<?php
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
// ...
$app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) {
$response->getBody()->write('Hello, World!');
return $response;
});
Giao diện như vậy có thể trông trực quan, nhưng không phù hợp cho các kịch bản phức tạp hơn.
Các hàm ẩn danh (anonymous functions) như trình xử lý định tuyến khá "đắt đỏ", vì PHP phải tạo tất cả các hàm cho mỗi yêu cầu. Việc sử dụng tên lớp nhẹ hơn, nhanh hơn và mở rộng tốt hơn cho các ứng dụng lớn hơn. Trừ khi logic của bạn rất đơn giản, tôi không khuyến nghị sử dụng các hàm callback. Đây là lúc Controller Hành Động Đơn (Single Action Controller) xuất hiện.
Mỗi Controller Hành Động Đơn được đại diện bởi một lớp riêng và chỉ có một phương thức công khai duy nhất.
Action chỉ làm những việc sau:
- Thu thập đầu vào từ yêu cầu HTTP (nếu cần)
- Gọi Domain với các đầu vào đó (nếu yêu cầu) và giữ lại kết quả
- Xây dựng một phản hồi HTTP (thường với kết quả gọi Domain)
Tất cả các logic khác, bao gồm tất cả các hình thức xác thực đầu vào, xử lý lỗi, v.v., do đó được đẩy ra khỏi Action và vào Domain (cho các vấn đề logic domain) hoặc bộ xử lý phản hồi (cho các vấn đề logic trình bày).
Một phản hồi có thể được hiển thị dưới dạng HTML cho một yêu cầu web tiêu chuẩn; hoặc có thể là JSON cho một RESTful API.
Tạo thư mục con: src/Action
Tạo lớp action này trong: src/Action/HomeAction.php
<?php
namespace AppAction;
use PsrHttpMessageResponseInterface as Response;
use PsrHttpMessageServerRequestInterface as Request;
final class HomeAction
{
public function __invoke(Request $request, Response $response): Response
{
$response->getBody()->write('Hello, World!');
return $response;
}
}
Sau đó, mở config/routes.php và thay thế closure của route / bằng dòng sau:
$app->get('/', AppActionHomeAction::class);
Tệp config/routes.php hoàn chỉnh bây giờ sẽ trông như sau:
<?php
use SlimApp;
return function (App $app) {
$app->get('/', AppActionHomeAction::class);
};
Bây giờ, mở URL của bạn, ví dụ: http://localhost:8080 và bạn sẽ thấy thông điệp Hello, World!
Tạo Phản Hồi JSON
Để tạo một phản hồi JSON hợp lệ, bạn có thể viết chuỗi đã mã hóa JSON vào thân phản hồi và đặt tiêu đề Content-Type thành application/json:
<?php
namespace AppAction;
use PsrHttpMessageResponseInterface as Response;
use PsrHttpMessageServerRequestInterface as Request;
final class HomeAction
{
public function __invoke(Request $request, Response $response): Response
{
$response->getBody()->write(json_encode(['hello' => 'world']));
return $response->withHeader('Content-Type', 'application/json');
}
}
Mở trang web của bạn, ví dụ: http://localhost:8080 và bạn sẽ thấy phản hồi JSON:
{"hello":"world"}
Kết Luận
Nhớ các mối quan hệ:
- Slim: Để xử lý định tuyến và phân phối
- Single Action Controllers: Xử lý yêu cầu và phản hồi
Slim tuân theo triết lý Unix về việc lựa chọn phần mềm và lập trình trong thời đại hiện đại, chọn và viết các chương trình thực hiện một việc và thực hiện nó tốt.
Slim cung cấp một nền tảng vững chắc cho các gói có thể được cài đặt khi cần. Nhờ các interface PSR, Composer và container DI, các gói có thể được thay thế một cách dễ dàng. Các bản cập nhật có thể được thực hiện theo các bước nhỏ mà không gây rủi ro quá nhiều.
Slim không ép buộc bạn phải tuân theo các khuôn khổ nghiêm ngặt như các framework lớn khác. Bạn cũng không bị ép buộc sử dụng bất kỳ anti-pattern nào (ví dụ: facade hoặc active-record), và bạn có thể xây dựng mã hiện đại, sạch sẽ.