微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

ApiPlatform - 标准化后将 DTO 的字段传递给不同的服务

如何解决ApiPlatform - 标准化后将 DTO 的字段传递给不同的服务

我有如下结构。

----------------
MESSAGE
----------------
id
subject
body
----------------


----------------
USER
----------------
id
name
category
region
----------------


----------------
RECIPIENT
----------------
user_id
message_id
is_read
read_at
----------------

所以Message 1:n Recipient m:1 User。 收件人不是 @ApiResource

后台用户将“编写”一条消息并根据一组特定条件(用户区域、用户类别、用户标签...)选择受众。

为了POST我正在使用 Dto 的消息

class MessageInputDto
{
    /**
     * @var string
     *
     * @Groups({"msg_message:write"})
     */
    public string $subject;
    /**
     * @var string
     *
     * @Groups({"msg_message:write"})
     */
    public string $body;
    /**
     * @var bool
     *
     * @Groups({"msg_message:write"})
     */
    public bool $isPublished;
    /**
     * @var DateTimeInterface
     *
     * @Groups({"msg_message:write"})
     */
    public DateTimeInterface $publishDate;
    /**
     * @var DateTimeInterface|null
     *
     * @Groups({"msg_message:write"})
     */
    public ?DateTimeInterface $expiryDate = null;
    /**
     * @var MessageCategory|null
     *
     * @Groups({"msg_message:write"})
     */
    public ?MessageCategory $category = null;
    /**
     * @var array
     */
    public array $criteria = [];
}

$criteria 字段用于选择该消息的受众,并被 DataTransformer 跳过,因为它不是映射字段,这是转换器返回的消息实体的属性

class MessageInputDataTransformer implements \ApiPlatform\Core\DataTransformer\DataTransformerInterface
{

    /**
     * @var MessageInputDto $object
     * @inheritDoc
     */
    public function transform($object,string $to,array $context = [])
    {
         $message = new Message($object->subject,$object->body);
         $message->setIsPublished($object->isPublished);
         $message->setPublishDate($object->publishDate);
         $message->setExpiryDate($object->expiryDate);
         $message->setCategory($object->category);

         return $message;
    }

    /**
     * @inheritDoc
     */
    public function supportsTransformation($data,array $context = []): bool
    {
        // in the case of an input,the value given here is an array (the JSON decoded).
        // if it's a book we transformed the data already
        if ($data instanceof Message) {
            return false;
        }

        return Message::class === $to && null !== ($context['input']['class'] ?? null);
    }
}

作为副作用,将在保持消息和用户间的 m:n 关系的连接表(接收者)中执行批量插入。

我的问题是如何/在何处执行此批量插入以及如何将 $criteria 传递给将管理它的服务。

我现在找到的唯一解决方案(它正在工作,但我认为这不是一个好习惯)是将批量插入过程放在 POST_WRITEMessage 事件中,获取 Request 对象并处理其中包含的 $criteria

class MessageSubscriber implements EventSubscriberInterface
{

    /**
     * @inheritDoc
     */
    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::VIEW => [
                ['handleCriteria',EventPriorities::POST_WRITE]
            ],];
    }

   
    public function handleCriteria(ViewEvent $event)
    {
        /** @var Message $message */
        $message = $event->getControllerResult();
        $method = $event->getRequest()->getmethod();
        $e = $event->getRequest();
        $collectionoperation = $e->get('_api_collection_operation_name');

        if (!$message instanceof Message ||
            $method !== Request::METHOD_POST ||
            $collectionoperation !== 'post') {
            return;
        }

        $content = json_decode($event->getRequest()->getContent(),true);

        if(array_key_exists('audienceCriteria',$content)){
            $criteria = Criteria::createFromArray($content['audienceCriteria']);
            // Todo: Create the audience
        }
    }
}

所以这个想法是,当消息被持久化时,系统必须生成公共的“关系”。

这就是为什么我认为 post write 事件可能是一个不错的选择,但正如我所说,我不确定这是否是一个好的做法。

有什么想法吗?谢谢。

解决方法

作为 DTO 状态的文档:“在大多数情况下,DTO 模式应该使用 API 资源类来实现,该类表示通过 API 和自定义数据提供程序公开的公共数据模型。在这种情况下,用@ApiResource 标记的类将充当 DTO。”

IOW 指定输入或输出数据表示和 DataTransformer 是例外。如果 DTO 持有的数据多于实体,或者 dto 与实体不是一对一的(例如,使用分组依据的报表),则它不起作用。

这是您的 DTO 类作为资源:

namespace App\DTO;

use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
use App\Entity\Message;

/**
 * Class defining Message data transfer
 *
 * @ApiResource(
 *     denormalizationContext= {"groups" = {"msg_message:write"}},*     itemOperations={
 *     },*     collectionOperations={
 *          "post"={
 *             "path"="/messages",*             "openapi_context" = {
 *                 "summary"     = "Creates a Message",*                 "description" = "Creates a Message"
 *             }
 *          }
 *     },*     output=Message::class
 * )
 */
class MessageInputDto
{
    /**
     * @var string
     *
     * @Groups({"msg_message:write"})
     */
    public string $subject;
    /**
     * @var string
     *
     * @Groups({"msg_message:write"})
     */
    public string $body;
    /**
     * @var bool
     *
     * @Groups({"msg_message:write"})
     */
    public bool $isPublished;
    /**
     * @var \DateTimeInterface
     *
     * @Groups({"msg_message:write"})
     */
    public \DateTimeInterface $publishDate;
    /**
     * @var \DateTimeInterface|null
     *
     * @Groups({"msg_message:write"})
     */
    public ?\DateTimeInterface $expiryDate = null;
    /**
     * @var MessageCategory|null
     *
     * @Groups({"msg_message:write"})
     */
    public ?MessageCategory $category = null;
    /**
     * @var array
     * @Groups({"msg_message:write"})
     */
    public array $criteria = [];
}

确保您的类所在的文件夹在 api/config/packages/api_platform.yaml 的路径列表中。通常有以下配置:

api_platform:
    mapping:
        paths: ['%kernel.project_dir%/src/Entity']

如果 MessageInputDto 在 /src/DTO 中,请使其像:

api_platform:
    mapping:
        paths: 
          - '%kernel.project_dir%/src/Entity'
          - '%kernel.project_dir%/src/DTO'

发布操作可能与您的消息资源上的默认发布操作具有相同的路径。通过为您的 Message 资源显式定义 collectionOperations 来删除它,而无需“发布”。

MessageInputDto 的 post 操作会反序列化 MessageInputDto。您的 DataTransformer 不会对其进行操作,因此它会按原样到达 DataPersister:

namespace App\DataPersister;

use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use App\DTO\MessageInputDto;
use App\Entity\Message;
use Doctrine\Persistence\ManagerRegistry;
use App\DataTransformer\MessageInputDataTransformer;
use ApiPlatform\Core\Exception\InvalidArgumentException;

class MessageDataPersister implements ContextAwareDataPersisterInterface
{
    private $dataPersister;
    private $entityManager;
    private $dataTransformer;

    public function __construct(ContextAwareDataPersisterInterface $dataPersister,ManagerRegistry $managerRegistry,MessageInputDataTransformer $dataTransformer)
    {
        $this->dataPersister = $dataPersister;
        $this->entityManager = $managerRegistry->getManagerForClass(Message::class);
        $this->dataTransformer = $dataTransformer;
    }

    public function supports($data,array $context = []): bool
    {
        $transformationContext = ['input' => ['class' => Message::class]];

        return get_class($data) == MessageInputDto::class
            && $this->dataTransformer->supportsTransformation($data,Message::class,$transformationContext)
            && null !== $this->entityManager;
    }

    public function persist($data,array $context = [])
    {
        $message = $this->dataTransformer->transform($data,Message::class);

        // dataPersister will flush the entityManager but we do not want incomplete data inserted
        $this->entityManager->beginTransaction();
        $commit = true;

        $result = $this->dataPersister->persist($message,[]);

        if(!empty($data->criteria)){
            $criteria = Criteria::createFromArray($data->criteria);

            try {
                // Todo: Create the audience,preferably with a single INSERT query SELECTing FROM user_table WHERE meeting the criteria
            // (Or maybe better postpone until message is really sent,user region,category,tags may change over time)

            } catch (\Exception $e) {
                $commit = false;
                $this->entityManager->rollback();
            }
        }
        if ($commit) {
            $this->entityManager->commit();
        }

        return $result;
    }

    public function remove($data,array $context = [])
    {
        throw new InvalidArgumentException('Operation not supported: delete');
    }

}

(也许它应该被称为 MessageInputDtoDataPersister - 取决于你如何看待它)

即使启用了服务自动装配和自动配置,您仍然必须对其进行配置以获得正确的 dataPersister 以委托给:

# api/config/services.yaml
services:
    # ...
    'App\DataPersister\MessageDataPersister':
        arguments:
            $dataPersister: '@api_platform.doctrine.orm.data_persister'

这样您就不需要 MessageSubscriber。

请注意,反序列化和数据持久化(验证、安全后非规范化)之间的所有其他阶段都在 MessageInputDto 上工作。

,

当您必须生成多个自定义实体时,一种解决方案是使用数据持久化程序:https://api-platform.com/docs/core/data-persisters/

你有两个选择:

  1. 装饰教义持久器 - 这意味着信息仍将被教义保存,但您可以在此之前或之后做一些事情。
  2. 实现自定义持久器 - 保存您喜欢的消息和其他相关实体。或者做一些完全自定义的事情,根本不调用 Doctrine。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。