- Published on
Api platform 教程7---State Processors和State Providers
- Authors
-
-
- Name
- langziyang
-
Api platform 教程7---State Processors和State Providers
在api platform中,向API提交数据时,有一个Processor,可以在POST,PUT,PATCH和DELETE时,对数据进行处理。 比如上节我们提到的对密码进行加密。我们可以通过命令来创建一个processor
php bin/console make:state-processor
默认会在src/State目录存放该文件,但可能因为我们的文件会很多,管理不方便,所以我在开发过程中喜欢根据Entity名称来存放
# src/State/User/Processor/UserProcessor.php
<?php
namespace App\State\User\Processor;
use ApiPlatform\Doctrine\Common\State\PersistProcessor;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\User;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class UserProcessor implements ProcessorInterface
{
public function __construct(
#[Autowire(service: PersistProcessor::class)]
private ProcessorInterface $processor,
private UserPasswordHasherInterface $hasher
)
{
}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
{
assert($data instanceof User);
$data->setPassword($this->hasher->hashPassword($data, $data->getPassword()));
$this->processor->process($data, $operation, $uriVariables, $context);
return $data;
}
}
# src/Entity/User.php
#[ApiResource(
operations: [
new GetCollection(),
new Get(),
new Post(processor: UserProcessor::class),
new Patch(processor: UserProcessor::class),
new Delete(),
]
)]
因为我们在post或者patch时都有可能加密密码,所以分别在两个操作上都加上了processor,如果整个Entity中每一个提交过程都需要加密, 比如说上面代码中的Delete也需要的话,你可以写在外面
#[ApiResource(
operations: [
new GetCollection(),
new Get(),
new Post(),
new Patch(),
new Delete(),
],
processor: UserProcessor::class
)]
提交数据后,我们可以看到,返回数据中的密码已经被加密了,但是代码中的逻辑判断需要你自己处理:比如说在修改用户信息时可能不会提供密码。
对于processor我们已经了解了,接下来我们看看provider。在Get和GetCollection操作时,有一个provider过程,在这里进行的操作会返回给我们 所有想要的数据。
# src/State/User/Provider/UserProvider.php
<?php
namespace App\State\User\Provider;
use ApiPlatform\Doctrine\Orm\State\CollectionProvider;
use ApiPlatform\Doctrine\Orm\State\ItemProvider;
use ApiPlatform\Metadata\CollectionOperationInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Entity\User;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
class UserProvider implements ProviderInterface
{
public function __construct(
#[Autowire(service: CollectionProvider::class)]
private ProviderInterface $collectionProvider,
#[Autowire(service: ItemProvider::class)]
private ProviderInterface $itemProvider,
)
{
}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
if ($operation instanceof CollectionOperationInterface) {
return $this->collectionProvider->provide($operation, $uriVariables, $context);
}
$user = $this->itemProvider->provide($operation, $uriVariables, $context);
assert($user instanceof User);
$user->setEmail('123@qq.com');
return $user;
}
}
#src/Entity/User.php
#[ApiResource(
operations: [
new GetCollection(provider: UserProvider::class),
new Get(provider: UserProvider::class),
new Post(processor: UserProcessor::class),
new Patch(processor: UserProcessor::class),
new Delete(),
]
)]
可以看到,我们在GetCollection和Get中写上了provider,当前,你也可以只在需要的操作中提供。
然后通过$operation判断如果是collection,就返回原来的数据,并没有对数据进行任何修改。
在get中,我们查找出对应的User,然后修改了一下email。但是现在的问题是:如果我希望无论是collection还是get,都去修改用户 的字段或者说再多添加一个返回字段呢?当然,你可以在collection中对取出来的数据进行foreach进行修改。如果想添加数据的话还需要去 Entity文件中添加字段。这不是一个好办法
其实Api platform在返回jsonld数据时是使用的symfony的序列化过程,所以我们可以写一个对应的过程
php bin/console make:serializer:normalizer
<?php
namespace App\Serializer\Normalizer;
use App\Entity\User;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class UserNormalizer implements NormalizerInterface
{
private const USER_ALREADY_NORMALIZED = 'user_already_normalized';
public function __construct(
#[Autowire(service: 'serializer.normalizer.object')]
private NormalizerInterface $normalizer
)
{
}
public function normalize($object, ?string $format = null, array $context = []): array
{
$context[self::USER_ALREADY_NORMALIZED] = true;
$data = $this->normalizer->normalize($object, $format, $context);
$data['custom']='custom_normalized';
return $data;
}
public function supportsNormalization($data, ?string $format = null, array $context = []): bool
{
return $data instanceof User && !isset($context[self::USER_ALREADY_NORMALIZED]);
}
public function getSupportedTypes(?string $format): array
{
return [User::class => true];
}
}