helmich / flow-resttools
用于使用TYPO3 Flow创建RESTful Web服务的实用包
Requires
- typo3/flow: *
This package is auto-updated.
Last update: 2020-01-27 18:47:45 UTC
README
作者
Martin Helmich typo3@martin-helmich.de
概述
此包包含一组辅助类,用于实现使用TYPO3 Flow的RESTful Web服务。
它特别处理以下问题
- 控制器配置
- 序列化领域对象
- 请求体处理
- 异常处理
控制器配置
此包提供了一个RestController,您可以在实现控制器时使用它。请注意,此类**不**扩展Flow自己的RestController
类。
RestController
类包含一个默认视图配置,使控制器能够支持JSON、YAML、XML和MSGPACK表示形式(目前仅支持输出。输入仍由Flow的默认MediaConverter处理,它仅支持JSON和XML)。敬请期待更多内容。
序列化领域对象
此包包含将领域对象转换为表示的API。与TYPO3 Flow的JsonView
不同,它依赖于根据对象获取器方法返回的值自动构建表示,此包要求为每个领域模型提供显式的规范化类。我更喜欢这种设计,因为它提供了更多关于对象表示形式生成的控制。
通常,对象序列化分为两个步骤
-
规范化:将领域对象转换为普通PHP数组。此步骤是领域特定的。这意味着您必须为要展示的每个领域对象指定一个规范化器类。这些类必须实现
NormalizerInterface
(见源)并返回一个标量PHP类型--通常是(嵌套的)数组。 -
序列化:将规范化步骤生成的标量PHP类型转换为字符串表示形式。此步骤不是领域特定的。目前,有JSON、YAML和MessagePack的规范化器。
请求体处理
动机
关于Flow,最让我烦恼的一件事是它对请求体的处理有限。只有当您将资源包装在Flow可以将其映射到请求参数的包装对象中时,反序列化JSON体才会工作。
例如,考虑以下控制器操作
public function testAction(Product $product) { // ... }
为了成功映射$product
参数,您的JSON请求体还需要一个"product"
属性
{ "product": { # ... } }
解决方案
您可以使用注解 Rest\BodyParam
来表示一个控制器动作参数,该参数应从请求体中填充。
<?php namespace My\Example\RestApi\Controller; use Helmich\RestTools\Annotations as Rest; class TestController { /** * @Rest\BodyParam("$product", allowProperties={"name", "price"}) */ public function testAction(Product $product) { // ... } }
allowProperties
键也预先配置了属性映射器,允许映射一定集合的属性(这意味着您不需要在 initialize
方法中显式启用此功能)。
或者,您可以使用 allowAllProperties
键,并将其设置为 true
以允许映射所有属性(请谨慎使用,因为这可能会带来安全风险)。
/** * @Rest\BodyParam("$product", allowAllProperties=TRUE) */ public function testAction(Product $product) { // ... }
异常处理
此包使用它自己的异常处理程序覆盖了 Flow 的默认异常处理程序。错误处理程序(ProductionRestExceptionHandler
和 DevelopmentRestExceptionHandler
)将未捕获的异常作为 JSON 文档呈现,并尝试根据异常类型猜测适当的 HTTP 响应代码(例如,当属性映射发生错误时,将抛出 400 状态码)。
完整示例
考虑一个简单的域对象 My\Example\Domain\Model\Product
,它具有 name 和 quantity 属性。
首先,实现一个规范器,用于将这些类的实例转换为标量值。
<?php namespace My\Example\RestApi\Normalizer; use Helmich\RestTools\Rest\Normalizer\NormalizerInterface; use My\Example\Domain\Model\Product; use TYPO3\Flow\Annotations as Flow; use TYPO3\Flow\Persistence\PersistenceManagerInterface; class ProductNormalizer implements NormalizerInterface { /** * @var PersistenceManagerInterface * @Flow\Inject */ protected $persistenceManager; public function objectToScalar($object) { if ($object instanceof Product) { return [ 'id' => $this->persistenceManager->getIdentifierByObject($object), 'name' => $object->getName(), 'amount_in_stock' => $object->getQuantity() ]; } } }
然后,在控制器中,您可以将此规范器连接到您的实体类。
<?php namespace My\Example\Controller; use My\Example\Domain\Model\Product; use My\Example\Domain\Repository\ProductRepository; use My\Example\RestApi\Normalizer\ManufacturerNormalizer; use Helmich\RestTools\Annotations as Rest; use Helmich\RestTools\Mvc\Controller\RestController; use Helmich\RestTools\Mvc\View\SerializingViewInterface; use TYPO3\Flow\Annotations as Flow; use TYPO3\Flow\Mvc\View\ViewInterface; class ProductController extends RestController { /** * @var ProductRepository * @Flow\Inject */ protected $productRepository; public function initializeView(ViewInterface $view) { if ($view instanceof SerializingViewInterface) { $view->registerNormalizerForClass(Product::class, new new ProductNormalizer()); } } public function listAction() { if ($view instanceof SerializingViewInterface) { $this->view->setRootElement('products'); } $this->view->assign('products', $this->productRepository->findAll()); } /** * @Rest\BodyParam("$product", allowAllProperties=TRUE) */ public function createAction(Product $product) { $this->productRepository->add($product); } }