sanderdlm / mono
小型框架,大梦想
Requires
- php: ^8.2 || ^8.3
- cuyz/valinor: ^1.9
- laminas/laminas-diactoros: ^3.3
- laminas/laminas-httphandlerrunner: ^2.9
- nikic/fast-route: ^1.3
- php-di/php-di: ^7.0
- relay/relay: ^2.1
- twig/twig: ^3.7
Requires (Dev)
- phpstan/phpstan: ^1.9
- phpunit/phpunit: ^10
- squizlabs/php_codesniffer: ^3.8
- symfony/var-dumper: ^7.0
This package is auto-updated.
Last update: 2024-09-14 20:40:50 UTC
README
Mono 是一个单文件的最小化现代 PHP 框架。
它不包含自定义实现,而是作为以下优秀项目的包装器
- 路由(使用 nikic/FastRoute)
- 依赖注入(使用 php-di/php-di)
- 中间件(使用 relay/relay)
- 模板(使用 twigphp/wig)
- 数据到对象映射(使用 cuyz/valinor)
这是 Mono
类的接口
interface MonoInterface { // Render a Twig template public function render(string $template, array $data): string; // Add a middleware to the PSR-15 stack public function addMiddleware(MiddlewareInterface|callable $middleware): void; // Add a route to the FastRoute router public function addRoute(string|array $method, string $path, callable $handler): void; // Run the app public function run(): void; }
仅使用这些方法,我们就拥有了构建应用程序所需的所有工具
<?php $mono = new Mono(__DIR__ . '/templates'); $mono->addRoute('GET', '/example', function() { $result = $mono->get(SomeDependency::class)->doSomething(); return new HtmlResponse($mono->render('example.twig', [ 'result' => $result ])); }); $mono->run();
Mono 不打算作为一个完整的框架,而是作为一个小型 PHP 应用程序的原型。目标是展示通过结合知名库和 PSR 实现,你可以走多远。
源代码 有注释,易于阅读。
1. 路由
您使用 $mono->addRoute()
添加所有路由。与底层 FastRoute 方法的签名相同。默认情况下,路由处理程序是闭包,因为此框架主要用于小型应用程序,但您也可以使用可调用的控制器。
有关路由模式的更多信息,请参阅 FastRoute 文档。输入的路径直接传递给 FastRoute。
闭包的第一个参数是始终当前请求,它是一个 PSR-7 ServerRequestInterface 对象。之后,下一个参数是路由参数。
当调用 $mono->run()
时,当前请求与您添加的路由进行匹配,调用闭包并发出响应。
1.1 使用闭包的示例
<?php $mono = new Mono(); $mono->addRoute('GET', '/books/{book}', function(ServerRequestInterface $request, string $book) { return new TextResponse(200, 'Book: ' . $book); }); $mono->run();
1.2 使用控制器的示例
<?php class BookController { public function __construct( private readonly Mono $mono ) { } public function __invoke(ServerRequestInterface $request, string $book): ResponseInterface { return new TextResponse('Book: ' . $book'); } }
<?php $mono = new Mono(); // By fetching the controller from the container, it will autowire all constructor parameters. $mono->addRoute('GET', '/books/{book}', $mono->get(BookController::class)); $mono->run();
在生产中缓存您的路由是微不足道的。将您的缓存文件的有效、可写路径作为 routeCacheFile
参数传递给您的 Mono
对象。
$mono = new Mono(routeCacheFile: sys_get_temp_dir() . '/mono-route.cache');
2. 依赖注入
当创建 Mono 对象时,它会使用默认配置构建一个基本的 PHP-DI 容器。这意味着任何加载的类(例如通过 PSR-4)都可以自动装配或手动从容器中获取。
您可以使用 Mono 对象上的 get()
方法从容器中检索实例。
<?php $mono = new Mono(); $mono->addRoute('GET', '/example', function() use ($mono) { $result = $mono->get(SomeDependency::class)->doSomething(); return new JsonResponse($result); }); $mono->run();
自定义容器
如果您需要定义自定义定义,您可以将自定义容器传递给 Mono 构造函数。有关更多信息,请参阅 PHP-DI 文档。
<?php // Custom container $builder = new DI\ContainerBuilder(); $builder->... // Add some custom definitions $container = $builder->build(); $mono = new Mono(container: $container); $mono->addRoute('GET', '/example', function() use ($mono) { $result = $mono->get(SomeDependency::class)->doSomething(); return new JsonResponse($result); }); $mono->run();
3. 中间件
Mono 构建为一个中间件堆栈应用程序。默认流程是
- 错误处理
- 路由(路由与处理程序匹配)
- 您的自定义中间件
- 请求处理(调用路由处理程序)
您可以使用 addMiddleware()
方法向堆栈添加中间件。中间件可以是可调用的或实现 MiddlewareInterface
接口的类。中间件的执行顺序与添加顺序相同。
<?php $mono = new Mono(); $mono->addMiddleware(function (ServerRequestInterface $request, callable $next) { // Do something before the request is handled if ($request->getUri()->getPath() === '/example') { return new TextResponse('Forbidden', 403); } return $next($request); }); $mono->addMiddleware(function (ServerRequestInterface $request, callable $next) { $response = $next($request); // Do something after the request is handled return $response->withHeader('X-Test', 'Hello, world!'); });
您可以在 middlewares/psr15-middlewares 项目中找到许多已经编写好的、兼容PSR-15的中间件。这些中间件可以直接插入到Mono中使用。
4. 模板化
如果您想使用Twig,需要在Mono构造函数中传入您的模板文件夹路径。
之后,您可以在您的Mono对象上使用 render()
方法来渲染该文件夹中的Twig模板。
<?php $mono = new Mono(__DIR__ . '/templates'); $mono->addRoute('GET', '/example', function() use ($mono) { $result = $mono->get(SomeDependency::class)->doSomething(); return new HtmlResponse($mono->render('example.twig', [ 'result' => $result ])); }); $mono->run();
5. 数据到对象的映射
这允许您将数据(例如,来自请求的POST体)映射到对象(例如,DTO)。
<?php class BookDataTransferObject { public function __construct( public string $title, public ?int $rating, ) { } } $_POST['title'] = 'Moby dick'; $_POST['rating'] = 10; $mono = new Mono(); $mono->addRoute('POST', '/book', function ( ServerRequestInterface $request ) use ($mono) { /* * $bookDataTransferObject now holds * all the data from the request body, * mapped to the properties of the class. */ $bookDataTransferObject = $mono->get(TreeMapper::class)->map( BookDataTransferObject::class, $request->getParsedBody() ); }); $mono->run();
如果您想覆盖默认的映射行为,定义一个自定义的 Treemapper
实现,并将其设置在传递给 Mono
构造函数的容器中。
自定义映射器配置示例
$customMapper = (new MapperBuilder()) ->enableFlexibleCasting() ->allowPermissiveTypes() ->mapper(); $containerBuilder = new ContainerBuilder(); $containerBuilder->useAutowiring(true); $containerBuilder->addDefinitions([ TreeMapper::class => $customMapper ]); $mono = new Mono( container: $containerBuilder->build() );
其他
调试模式
Mono有一个默认会捕获所有错误并显示通用500响应的调试模式。
在开发过程中,您可以通过将 false
作为Mono构造函数的第二个参数传入来禁用此模式。这将显示实际的错误消息,并允许您在Twig模板中使用 dump
。
文件夹结构 & 项目设置
开始一个新项目很快。按照以下步骤操作
- 为您的项目创建一个新的文件夹。
- 运行
composer require sanderdlm/mono
。 - 在项目根目录下创建一个
public
文件夹。添加一个index.php
文件。下面有一个“Hello world”示例。 - 可选地,在项目根目录下创建一个
templates
文件夹。添加一个home.twig
文件。下面有一个示例。 - 运行
php -S localhost:8000 -t public
以启动内置的PHP服务器。 - 开始开发您的想法!
public/index.php
:
<?php declare(strict_types=1); use Mono\Mono; require_once __DIR__ . '/../vendor/autoload.php'; $mono = new Mono(__DIR__.'/../templates'); $mono->addRoute('GET', '/', function() { return new HtmlResponse($mono->render('home.twig', [ 'message' => 'Hello world!', ])); }); $mono->run();
templates/home.twig
:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Home</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> {{ message }} </body> </html>
如果您想保持简单,可以直接在index.php中工作。如果您需要定义多个文件/类,可以在项目中添加一个 src
文件夹,并将以下PSR-4自动加载片段添加到您的 composer.json
文件中
"autoload": { "psr-4": { "App\\": "src/" } },
您现在可以从DI容器中访问 src
文件夹中的所有类(并自动注入它们)。
其他包
以下包与Mono配合得非常好。其中大部分可以通过Composer快速安装,然后通过向容器中添加定义来配置。
- vlucas/phpdotenv 用于环境变量
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..'); $dotenv->load();
- symfony/validator 用于请求DTO的验证
// Custom container $containerBuilder = new \DI\ContainerBuilder(); $containerBuilder->useAutowiring(true); $containerBuilder->addDefinitions([ // Build & pass a validator ValidatorInterface::class => Validation::createValidatorBuilder() ->enableAttributeMapping() ->getValidator(), ]); $mono = new Mono( container: $containerBuilder->build() ); // Generic sign-up route $mono->addRoute('POST', '/sign-up', function( ServerRequestInterface $request ) use ($mono) { // Build the DTO $personDataTransferObject = $mono->get(TreeMapper::class)->map( PersonDataTransferObject::class, $request->getParsedBody() ); // Validate the DTO $errors = $mono->get(ValidatorInterface::class)->validate($personDataTransferObject); // Do something with the errors if ($errors->count() > 0) { } });
- brefphp/bref 用于部署到AWS Lambda
- symfony/translation 用于实现国际化(也推荐使用 symfony/twig-bridge)
// Create a new translator, with whatever config you like. $translator = new Translator('en'); $translator->addLoader('array', new ArrayLoader()); $translator->addResource('array', [ 'hello_world' => 'Hello world!', ], 'en'); // Create a custom container & add your translator to it $containerBuilder = new \DI\ContainerBuilder(); $containerBuilder->useAutowiring(true); $containerBuilder->addDefinitions([ TranslatorInterface::class => $translator, ]); $mono = new Mono( container: $containerBuilder->build() ); /* * Use the |trans filter in your Twig templates, * or get the TranslatorInterface from the container * and use it directly in your route handlers. */