zfegg / psr-mvc
用于PSR应用程序的MVC,例如dotnet core MVC。
3.1.0
2024-07-27 17:01 UTC
Requires
- php: >=8.0
- psr/container: ^1.0 || ^2.0
- psr/http-server-handler: ^1.0
Requires (Dev)
- laminas/laminas-config-aggregator: ^1.7
- laminas/laminas-di: ^3.14
- laminas/laminas-diactoros: ^3.0
- laminas/laminas-servicemanager: ^3.6
- mezzio/mezzio: ^3.6
- mezzio/mezzio-fastroute: ^3.3
- mezzio/mezzio-router: ^3.7.0
- monolog/monolog: ^2.0 || ^3.0
- phpunit/phpunit: ^9.5
- slevomat/coding-standard: ^8.15.0
- symfony/property-access: ^5.4 || ^6.0
- symfony/serializer: ^5.4 || ^6.0
- willdurand/negotiation: ^3.0
- zfegg/expressive-test: ^0.7.1
Suggests
- symfony/serializer: Serialize action result required.
This package is auto-updated.
Last update: 2024-08-27 17:07:00 UTC
README
英语 | 简体中文
PSR MVC处理器
使用MVC风格为PSR处理器应用程序,如dotnet core MVC。
使用PHP属性(注解),将控制器转换为PSR15 RequestHandlerInterface
对象。
安装
composer require zfegg/psr-mvc
使用
属性使用示例,如dotnet core MVC
MVC路由
Mezzio入门
// File config/config.php // Add ConfigProvider new ConfigAggregator([ Zfegg\PsrMvc\ConfigProvider::class, ]);
// config/autoload/global.php use Zfegg\PsrMvc\Container\HandlerFactory; return [ // Add scan controllers paths \Zfegg\PsrMvc\Routing\RouteMetadata::class => [ 'paths' => ['path/to/Controller'], 'cacheFile' => 'data/cache/route-meta.php', // For cache routes ] ]; // path/to/Controller/HomeController.php public class HomeController { #[Route("/")] #[Route("/home")] #[Route("/home/index")] #[Route("/home/index/{id?}")] public index(?int $id) { return new HtmlResponse(); } #[Route("/home/about")] #[Route("/home/about/{id}")] public about(?int $id) { return new HtmlResponse(); } }
属性
Route(string $path, array $middlewares = [], ?string $name = null, array $options = [], ?array $methods = null)
HttpGet(string $path, array $middlewares = [], ?string $name = null, array $options = [])
HttpPost(string $path, array $middlewares = [], ?string $name = null, array $options = [])
HttpPatch(string $path, array $middlewares = [], ?string $name = null, array $options = [])
HttpPut(string $path, array $middlewares = [], ?string $name = null, array $options = [])
HttpDelete(string $path, array $middlewares = [], ?string $name = null, array $options = [])
HttpHead(string $path, array $middlewares = [], ?string $name = null, array $options = [])
通过PHP属性注册路由。
return [ RouteMetadata::class => [ // Scan controller paths. 'paths' => [ 'path/Controller', ], ], ]
以下代码将#[Route("/[controller]/[action]")]
应用于控制器
public class HomeController { #[Route("/")] #[Route("/home")] #[Route("/home/index")] #[Route("/home/index/{id?}")] public index(?int $id) { return new HtmlResponse(); } #[Route("/home/about")] #[Route("/home/about/{id}")] public about(?int $id) { return new HtmlResponse(); } }
组合属性路由
use Psr\Http\Message\ResponseInterface; #[Route("/api/[controller]")] // Route prefix `/api/products` class ProductsController { #[HttpGet] // GET /api/products public function listProducts(): array { return $db->fetchAllProducts(); } // Route path `/api/products/{id}` #[HttpGet('{id}')] // GET /api/products/123 public function getProduct(int $id): object { return $db->find($id); } #[HttpPost] // POST /api/products public function create(#[FromBody(root: true)] array $data): object { $db->save($data); // ... return $db->find($lastInsertId); } }
包装控制器处理器
使用参数属性
FromAttribute(?string $name = null)
$name
默认为参数名称
FromBody(?string $name = null, ?bool $root = false, array $serializerContext = [])
$name
默认为参数名称
FromContainer(?string $name = null)
$name
默认为参数类型
FromCookie(?string $name = null)
$name
默认为参数名称
FromHeader(?string $name = null)
$name
默认为参数名称
FromQuery(?string $name = null)
$name
默认为参数名称
FromServer(string $name)
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; /* POST /api/example/hello?page=1 Host: localhost Cookie: PHPSESSION=xxx name=foo */ class ExampleController { #[HttpPost('/api/[controller]/[action]')] public function post( #[FromQuery] int $page, // 1 #[FromBody] string $name, // "foo" #[FromContainer('db')] \PDO $container, // object(PDO) #[FromCookie('PHPSESSION')] string $sessionId, // "xxx" #[FromHeader] string $host, // "localhost" #[FromServer('REMOTE_ADDR')] string $ip, // "127.0.0.1" ): void { return ; } // Default binding params #[HttpPost('/api/[controller]/[action]/{id}')] public function hello( ServerRequestInterface $request, // Default bind `$request`. int $id, // Default bind `$request->getAttribute('id')`. Foo $foo, // If container exists the `Foo`, default bind `$container->get('id')`. Bar $bar, // Default bind `$request->getAttribute(Bar::class, $request->getAttribute('bar'))`. ): void { } }
默认参数绑定
class ExampleController { #[HttpPost('/api/[controller]/[action]/{id}')] public function hello( ServerRequestInterface $request, // Default bind `$request`. int $id, // Default bind `$request->getAttribute('id')`. Foo $foo, // If container exists the `Foo`, default bind `$container->get('id')`. Bar $bar, // Default bind `$request->getAttribute(Bar::class, $request->getAttribute('bar'))`. ): void { } }
准备结果以供PSR响应。
解析各种类型的方法结果并将其转换为'ResponseInterface'。对于解析回调结果到ResponseInterface。
Zfegg\PsrMvc\Preparer\SerializationPreparer
class ExampleResponseController { #[HttpPost('/hello-void')] // `void` -> HTTP 204 No Content public function helloVoid(): void { } /* * If result is string, then convert to `HtmlResponse` object. * `new HtmlResponse($result)` */ #[HttpPost('/hello-string')] public function helloString(): string { return '<h1>Hello</h1>'; } /* * If result is array, default convert to `JsonResponse` object. * `new JsonResponse($result)` */ #[HttpPost('/hello-array')] public function helloArray(): array { return ['foo' => 'a', 'bar' => 'b']; } }
Zfegg\PsrMvc\Preparer\SerializationPreparer
(推荐)
使用symfony/serializer
进行序列化并将响应体写入。
class ExampleResponseController { #[HttpPost('/hello-void')] // `void` -> HTTP 204 No Content public function helloVoid(): void { } /* * Serialize by `symfony/serializer`. * The serialization format is parsed by `FormatMatcher`. * <code> * $result = $serializer->serialize($result, $format); * $response->withBody($result); * </code> */ #[HttpPost('/hello-foo')] public function hello(): Foo { return new Foo(); } }
准备器选项
PrepareResult
属性
使用#[PrepareResult]
属性选择准备器并传递上下文。
use \Zfegg\PsrMvc\Preparer\SerializationPreparer; use Zfegg\PsrMvc\Attribute\PrepareResult; class ExampleResponseController { #[HttpPost('/hello-void')] // `void` -> HTTP 204 No Content public function helloVoid(): void { } /* * 选用 `SerializationPreparer` 预处理器, 处理结果. */ #[HttpPost('/hello-foo')] #[PrepareResult(SerializationPreparer::class, ['status' => 201, 'headers' => ['X-Test' => 'foo']])] public function hello(): Foo { return new Foo(); } }
Mezzio示例
// Class file HelloController.php class HelloController { public function say( \Psr\Http\Message\ServerRequestInterface $request, // Inject request param string $name, // Auto inject param from $request->getAttribute('name'). Foo $foo // Auto inject param from container. ) { return new TextResponse('hello ' . $name); } }
// File config/config.php // Add ConfigProvider new ConfigAggregator([ Zfegg\PsrMvc\ConfigProvider::class, ]);
// config/autoload/global.php // Add demo class factories use Zfegg\PsrMvc\Container\HandlerFactory; return [ 'dependencies' => [ 'invokables' => [ Hello::class, ], 'factories' => [ Hello::class . '@say' => HandlerFactory::class, ], ] ];
不使用属性注册路由。
使用CallableHandlerAbstractFactory
注册路由。
// config/autoload/global.php // Add demo class factories use Zfegg\PsrMvc\Container\CallbackHandlerAbstractFactory; return [ 'dependencies' => [ 'factories' => [ ExampleController::class . '@fooMethod' => CallbackHandlerAbstractFactory::class, ], ] ]; $app->get('/foo-method', ExampleController::class . '@fooMethod')
在laminas/laminias-servicemanager
中注册抽象工厂。
// config/autoload/global.php // Add demo class factories use Zfegg\PsrMvc\Container\CallbackHandlerAbstractFactory; return [ 'dependencies' => [ 'invokables' => [ Hello::class, ], 'abstract_factories' => [ CallbackHandlerAbstractFactory::class, ], ] ]; class User { function create() {} function getList() {} function get($id) {} function delete($id) {} } // CallableHandlerDecorator abstract factory. $container->get('User@create'); $container->get('User@getList'); $container->get('User@get'); $container->get('User@delete');
mezzio的错误处理器
丰富的错误处理
以JSON格式响应
在处理器中抛出异常。
use \Zfegg\PsrMvc\Exception\AccessDeniedHttpException; use \Zfegg\PsrMvc\Attribute\HttpGet; class FooController { #[HttpGet("/api/foo")] public function fooAction() { throw new AccessDeniedHttpException("Foo", code: 100); } }
当请求是ajax时,将响应为JSON结果
HTTP/1.1 403 Forbidden
{"message":"Foo","code":100}
记录错误
当出现错误时,您可能希望监听它们以提供日志记录等特性。请参阅https://docs.mezzio.dev/mezzio/v3/features/error-handling/#listening-for-errors
use Laminas\Stratigility\Middleware\ErrorHandler; use Zfegg\PsrMvc\Container\LoggingError\LoggingErrorDelegator; return [ 'dependencies' => [ 'delegators' => [ ErrorHandler::class => [ LoggingErrorDelegator::class, ], ], ], ];