maba/fork-paysera-lib-rest-bundle

该软件包最新版本(4.4.1)没有可用的许可证信息。

Symfony 扩展包,允许您轻松配置 REST 端点。

该软件包的官方仓库似乎已不存在,因此该软件包已被冻结。

安装: 490

依赖者: 0

建议者: 0

安全: 0

星标: 0

关注者: 0

分支: 9

类型:symfony-bundle

4.4.1 2019-06-03 15:25 UTC

README

Latest Version on Packagist Software License Build Status Coverage Status Quality Score Total Downloads

Symfony 扩展包,允许您轻松配置 REST 端点。

master 分支在此存储库中已更改命名空间(Paysera\Bundle\RestBundle -> Maba\Bundle\RestBundle)和容器扩展(paysera_rest -> maba_paysera_rest)。

这样做是为了使分叉可以通过 maba/fork-paysera-lib-rest-bundle 软件包与旧版扩展包一起进行测试。

请参阅此存储库中的 paysera 分支以获取原始分叉。

为什么?

如果您编写了相当多的 REST 端点,一些代码或结构本身会重复。如果您想为所有端点添加一些功能,如果为每个端点都编写了一些自定义代码,这也可能变得麻烦。

与 API Platform 的区别

API Platform 提供了许多 API 规范选项、文档生成等功能。由于它知道您对象中的所有关系和字段,因此这些功能都是可用的。但是,为了这样做,您需要配置对象以启用所有这些功能,包括序列化选项。

这种方法对于小型应用程序来说非常完美,但对于大型应用程序或需要长期支持且经常更改的应用程序来说,可能会很痛苦。

当需要一些自定义功能时,在代码中实现它比正确配置它(如果此类配置甚至可用)要容易得多。

此扩展包提供了一些更多控制

  • 每个路由都明确定义,并执行相应的控制器操作。这允许更好地跟踪执行并在使用时使用任何自定义编程代码;
  • 对于序列化/规范化代码,使用的是代码而不是配置。这也使其更加明确和可配置。将 REST 接口与业务模型紧密耦合似乎并不是一个好的主意。

这有点更像模板代码,但需要在时很容易进行自定义。

安装

composer require maba/fork-paysera-lib-rest-bundle

如果您没有使用 symfony flex,请将以下扩展包添加到您的内核中

new PayseraNormalizationBundle(),
new \Maba\Bundle\RestBundle\PayseraRestBundle(),

配置

maba_paysera_rest:
    locales: ['en', 'lt', 'lv']     # Optional list of accepted locales
    validation:
        property_path_converter: your_service_id    # Optional service ID to use for property path converter

使用

基本示例

创建资源

为了从请求和响应中规范化/反规范化数据,使用 PayseraNormalizationBundle。它通过为您的每个资源编写一个类来实现。这使得它明确,并允许轻松自定义以映射到/从您的域模型(通常有 Doctrine 实体)。

规范化示例

<?php
declare(strict_types=1);

use Paysera\Component\Normalization\ObjectDenormalizerInterface;
use Paysera\Component\Normalization\NormalizerInterface;
use Paysera\Component\Normalization\TypeAwareInterface;
use Paysera\Component\Normalization\DenormalizationContext;
use Paysera\Component\Normalization\NormalizationContext;
use Paysera\Component\ObjectWrapper\ObjectWrapper;

class UserNormalizer implements ObjectDenormalizerInterface, NormalizerInterface, TypeAwareInterface
{
    public function denormalize(ObjectWrapper $input, DenormalizationContext $context)
    {
        return (new User())
            ->setEmail($input->getRequiredString('email'))
            ->setPlainPassword($input->getRequiredString('password'))
            ->setAddress($context->denormalize($input->getObject('address'), Address::class))
        ;
    }

    public function normalize($user, NormalizationContext $normalizationContext)
    {
        return [
            'email' => $user->getEmail(),
            'address' => $user->getAddress(),   // will be mapped automatically if type is classname
        ];
    }

    public function getType(): string
    {
        return User::class; // you can use anything here, but types can be guessed if FQCN are used
    }
}

最简单的方法是使用注解配置 REST 端点。这需要您的路由也由控制器注解提供。

控制器示例

<?php
declare(strict_types=1);

use Symfony\Component\Routing\Annotation\Route;
use Maba\Bundle\RestBundle\Annotation\Body;

class ApiController
{
    // ...

    /**
     * @Route("/users", method="POST")
     * @Body(parameterName="user")
     * 
     * @param User $user
     * @return User
     */
    public function register(User $user)
    {
        $this->securityChecker->checkPermissions(Permissions::REGISTER_USER, $user);
        
        $this->userManager->registerUser($user);
        $this->entityManager->flush();
        
        return $user;
    }
}

资源获取

控制器示例

<?php
declare(strict_types=1);

use Symfony\Component\Routing\Annotation\Route;
use Maba\Bundle\RestBundle\Annotation\PathAttribute;

class ApiController
{
    // ...
    
    /**
     * @Route("/users/{userId}", method="GET")
     * @PathAttribute(parameterName="user", pathPartName="userId")
     * 
     * @param User $user
     * @return User
     */
    public function getUser(User $user)
    {
        $this->securityChecker->checkPermissions(Permissions::ACCESS_USER, $user);
        
        return $user;
    }
}

对于路径属性,应实现 PathAttributeResolverInterface,因为在这种情况下我们只收到一个标量类型(ID),而不是一个对象。

默认情况下,包尝试查找在类型提示中以完全限定类名注册的类型对应的解析器。

您有多种选项可以使它工作。

  1. 创建自己的路径属性解析器。例如
<?php
declare(strict_types=1);

use Maba\Bundle\RestBundle\Service\PathAttributeResolver\PathAttributeResolverInterface;

class FindUserPathAttributeResolver implements PathAttributeResolverInterface
{
    // ...

    public function resolveFromAttribute($attributeValue)
    {
        return $this->repository->find($attributeValue);
    }
}

使用标签服务 paysera_rest.path_attribute_resolver,提供作为 type 属性的 FQCN。

  1. 重用 DoctrinePathAttributeResolver 类来配置所需的服务。例如
<service class="Maba\Bundle\RestBundle\Service\PathAttributeResolver\DoctrinePathAttributeResolver"
         id="find_user_denormalizer">
    <tag name="paysera_rest.path_attribute_resolver" type="App\Entity\User"/>

    <argument type="service">
        <service class="App\Repository\UserRepository">
            <factory service="doctrine.orm.entity_manager" method="getRepository"/>
            <argument>App\Entity\User</argument>
        </service>
    </argument>
    <argument>id</argument><!-- or any other field to search by -->
</service>
  1. config.yml 中配置支持类和搜索字段。这实际上与前面的选项相同。
paysera_rest:
    path_attribute_resolvers:
        App\Entity\User: ~  # defaults to searching by "id"
        App\Entity\PersistedEntity:
            field: identifierField

获取资源列表

控制器示例

<?php
declare(strict_types=1);

use Symfony\Component\Routing\Annotation\Route;
use Maba\Bundle\RestBundle\Annotation\Query;
use Paysera\Pagination\Entity\Pager;

class ApiController
{
    // ...
    
    /**
     * @Route("/users", method="GET")
     * @Query(parameterName="filter")
     * @Query(parameterName="pager")
     * 
     * @param UserFilter $filter
     * @param Pager $pager
     * @return User
     */
    public function getUsers(UserFilter $filter, Pager $pager)
    {
        $this->securityChecker->checkPermissions(Permissions::SEARCH_USERS, $filter);
        
        $configuredQuery = $this->userRepository->buildConfiguredQuery($filter)
            ->setTotalCountNeeded(true) // total count will be returned only if this is called
            ->setMaximumOffset(100)     // you can optionally limit maximum offset
        ;
        return $this->resultProvider->getResultForQuery($configuredQuery, $pager);
    }
}

UserFilter 的反序列化器

<?php
declare(strict_types=1);

use Paysera\Component\Normalization\ObjectDenormalizerInterface;
use Paysera\Component\Normalization\TypeAwareInterface;
use Paysera\Component\Normalization\DenormalizationContext;
use Paysera\Component\ObjectWrapper\ObjectWrapper;

class UserFilterDenormalizer implements ObjectDenormalizerInterface, TypeAwareInterface
{
    public function denormalize(ObjectWrapper $input, DenormalizationContext $context)
    {
        return (new UserFilter())
            ->setEmail($input->getString('email'))
            ->setCountryCode($input->getString('country_code'))
        ;
    }

    public function getType(): string
    {
        return UserFilter::class; // you can use anything here, but types can be guessed if FQCN are used
    }
}

UserRepository 中的代码

<?php
declare(strict_types=1);

use Paysera\Pagination\Entity\OrderingConfiguration;
use Paysera\Pagination\Entity\Doctrine\ConfiguredQuery;

class UserRepository extends Repository
{
    public function buildConfiguredQuery(UserFilter $filter)
    {
        // just an example – should add conditions only when they're set
        $queryBuilder = $this->createQueryBuilder('u')
            ->join('u.address', 'a')
            ->join('a.country', 'c')
            ->andWhere('u.email = :email')
            ->andWhere('c.code = :countryCode')
            ->setParameter('email', $filter->getEmail())
            ->setParameter('countryCode', $filter->getCountryCode())
        ;
        
        return (new ConfiguredQuery($queryBuilder))
            ->addOrderingConfiguration('email', new OrderingConfiguration('u.email', 'email'))
            ->addOrderingConfiguration(
                'country_code',
                new OrderingConfiguration('c.code', 'address.country.code')
            )
        ;
    }
}

如本例所示,包集成了对 Paysera 分页组件 的支持。

最好将构建 ConfiguredQuery 的过程分为两部分,如示例所示

  • 仓库使用 doctrine 查询构建器构造对象并设置排序配置;
  • 控制器配置最大偏移量和总计数计算策略——这些与从数据库获取数据无关,应从仓库类的代码中分离。

如有需要,请参阅库文档以获取进一步的使用信息。

注解

Body

指示将请求体转换为对象,并将其作为参数传递给控制器。

选项名称 默认值 描述
parameterName 必需 指定参数名称(控制器动作中用于传递反序列化数据的名称,不包括 $
denormalizationType 从参数的类型提示中猜测 用于体数据反序列化的反序列化类型
可选 true 如果参数有默认值 允许覆盖请求体的要求。可选意味着可以传递空请求体

BodyContentType

配置允许的请求内容类型以及是否在传递给反序列化器之前对体进行 JSON 解码。

选项名称 默认值 描述
supportedContentTypes 必需 支持的内容类型值的数组。可以是 * 以允许任何内容类型,或者 something/* 以仅验证第一部分。例如,这可以用来只允许图像
jsonEncodedBody false 是否应在传递给反序列化器之前对内容进行 JSON 解码

如果没有配置,默认为 JSON 编码的体和 2 个允许的内容类型值:""(空)和 "application/json"

为了使此注解有任何效果,必须存在 Body 注解。如果想要跳过反序列化过程,请将 denormalizationType 提供为 plain

Validation

配置或关闭从请求体反序列化对象的验证。默认情况下,验证始终启用。

您可以关闭动作或整个控制器类的验证。

如果注解同时存在于类和动作上,动作上的注解“获胜”——它们不会合并。

选项名称 默认值 描述
enabled true 允许显式禁用验证
groups ['Default'] 用于验证的验证组。空组列表与禁用验证相同
violationPathMap [] 将属性路径转换为REST字段的关联数组。默认情况下,驼峰式命名已转换为下划线命名。如果您有命名或结构不匹配的情况,请使用此功能。

ResponseNormalization

配置用于方法返回值的归一化类型,如果您需要自定义类型。

默认情况下,REST端点会尝试归一化从控制器动作返回的任何非Response对象的价值。

如果方法没有返回任何内容(void),则提供空响应,HTTP状态码为204

选项名称 默认值 描述
normalizationType 从返回值中猜测 用于归一化方法返回值的归一化类型

PathAttribute

配置路径某些具体部分的反归一化。通常用于通过ID查找实体。

可以在单个控制器的动作中使用多个此类注解。

选项名称 默认值 描述
parameterName 必需 指定参数名称(控制器动作中用于传递反序列化数据的名称,不包括 $
pathPartName 必需 指定用于反归一化的路由属性(对于/users/{id}使用id
denormalizationType 从类型提示+:find后缀中猜测 允许配置自定义的反归一化类型。注册的反归一化器必须实现MixedTypeDenormalizerInterface
resolutionMandatory true如果参数没有默认值 指定如果参数未解析是否返回404错误

Query

指示将查询字符串转换为对象,并将其作为参数传递给控制器。

可以使用多个注解映射多个不同的对象。

选项名称 默认值 描述
parameterName 必需 指定参数名称(控制器动作中用于传递反序列化数据的名称,不包括 $
denormalizationType 从类型提示中猜测 允许配置自定义的反归一化类型。注册的反归一化器必须实现MixedTypeDenormalizerInterface
验证 使用['Default']组启用 在此处使用另一个@Validation注解,就像配置请求体验证一样

RequiredPermissions

指示在安全上下文中检查特定动作的权限。

也可以在类级别添加。类和方法级别的注解中的权限将合并。

选项名称 默认值 描述
permissions 必需 在反归一化之前要检查的权限列表

不使用注解进行配置

还可以配置定义RestRequestOptions的选项,作为服务并标记为paysera_rest.request_options

示例

<service id="paysera_fixture_test.rest_request_options.1"
         class="Maba\Bundle\RestBundle\Entity\RestRequestOptions">
    <tag name="paysera_rest.request_options" controller="service_id::action"/>
    <tag name="paysera_rest.request_options" controller="App\Controller\DefaultController::action"/>

    <!-- set any options similarly to this -->
    <call method="addQueryResolverOptions">
        <argument type="service">
            <service class="Maba\Bundle\RestBundle\Entity\QueryResolverOptions">
                <call method="setDenormalizationType">
                    <argument>extract:parameter</argument>
                </call>
                <call method="setParameterName">
                    <argument>parameter</argument>
                </call>
            </service>
        </argument>
    </call>
</service>

语义版本控制

此库遵循语义版本控制

有关API中可以更改的内容和不能更改的内容的基本信息,请参阅Symfony BC规则

请勿使用未标记为public="true"的任何服务以及标记为@internal的任何类或方法,因为这些可能在任何版本中更改。

运行测试

composer update
composer test

贡献

请随意创建问题和拉取请求。

您可以使用此命令修复任何代码风格问题

composer fix-cs