在官方Api-Platform网站上有一个General Design Considerations页面。
最后但并非最不重要的是,要创建基于事件采购的系统,一种方便的方法是:
- 使用自定义数据容器在事件存储中保留数据
- 在标准RDBMS(Postgres,MariaDB ...)表或视图中创建投影
- 使用只读Doctrine实体类映射这些投影,并使用@ApiResource标记这些类
然后,您可以从API平台提供的内置Doctrine过滤器,排序,分页,自动连接等中受益。
因此,我试图通过一种简化来实现这种方法(使用一个DB,但具有独立的读写)。
但是失败了......有一个问题,我不知道如何解决,所以请你帮忙!
我创建了一个User
Doctrine实体和我希望用@Serializer\Groups({"Read"})
公开的带注释的字段。我会在这里省略它,因为它非常通用。
适用于api平台的yaml格式的User
资源:
# config/api_platform/entities/user.yaml
App\Entity\User\User:
attributes:
normalization_context:
groups: ["Read"]
itemOperations:
get: ~
collectionOperations:
get:
access_control: "is_granted('ROLE_ADMIN')"
所以,正如上面所示,User
Doctrine实体是只读的,因为只定义了GET
方法。
然后我创建了一个CreateUser
DTO:
# src/Dto/User/CreateUser.php
namespace App\Dto\User;
use App\Validator as AppAssert;
use Symfony\Component\Validator\Constraints as Assert;
final class CreateUser
{
/**
* @var string
* @Assert\NotBlank()
* @Assert\Email()
* @AppAssert\FakeEmailChecker()
*/
public $email;
/**
* @var string
* @Assert\NotBlank()
* @AppAssert\PlainPassword()
*/
public $plainPassword;
}
适用于api平台的yaml格式的CreateUser
资源:
# config/api_platform/dtos/create_user.yaml
App\Dto\User\CreateUser:
itemOperations: {}
collectionOperations:
post:
access_control: "is_anonymous()"
path: "/users"
swagger_context:
tags: ["User"]
summary: "Create new User resource"
因此,在这里您可以看到只定义了一个POST
方法,完全用于创建新用户。
这里路由器显示:
$ bin/console debug:router
---------------------------------- -------- -------- ------ -----------------------
Name Method Scheme Host Path
---------------------------------- -------- -------- ------ -----------------------
api_create_users_post_collection POST ANY ANY /users
api_users_get_collection GET ANY ANY /users.{_format}
api_users_get_item GET ANY ANY /users/{id}.{_format}
我还添加了一个自定义DataPersister
来处理POST
到/users
。在CreateUserDataPersister::persist
中,我使用Doctrine实体来编写数据,但是对于这种情况并不重要,因为Api-platform对DataPersister将如何编写它一无所知。因此,从概念来看 - 它是读写分离。
读取由Doctrine的ap-platform附带的DataProvider
执行,写入由自定义DataPersister
执行。
# src/DataPersister/CreateUserDataPersister.php
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use App\Dto\User\CreateUser;
use App\Entity\User\User;
use Doctrine\ORM\EntityManagerInterface;
class CreateUserDataPersister implements DataPersisterInterface
{
private $manager;
public function __construct(EntityManagerInterface $manager)
{
$this->manager = $manager;
}
public function supports($data): bool
{
return $data instanceof CreateUser;
}
public function persist($data)
{
$user = new User();
$user
->setEmail($data->email)
->setPlainPassword($data->plainPassword);
$this->manager->persist($user);
$this->flush();
return $user;
}
public function remove($data)
{
}
}
当我执行创建新用户的请求时:
POST https://{{host}}/users
Content-Type: application/json
{
"email": "[email protected]",
"plainPassword": "123qweQWE"
}
问题!我得到了400
响应... "hydra:description": "No item route associated with the type "App\Dto\User\CreateUser"." ...
但是,新记录被添加到数据库,因此自定义DataPersister工作;)
根据General Design Considerations,写入和读取的分离已实现,但未按预期工作。
我很确定,我可能会遗漏一些配置或实现的东西。所以,这就是为什么它不起作用。
很乐意得到任何帮助!
更新1:
问题出在\ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameResolver::getRouteName()
。在第48-59行,它遍历试图找到适当路线的所有路线:
$operationType = 'item'
$resourceClass = 'App\Dto\User\CreateUser'
但$operationType = 'item'
仅针对$resourceClass = 'App\Entity\User\User'
定义,因此无法找到路线并引发异常。
更新2:
所以,问题可能听起来像这样:
如何使用Doctrine实体进行读取和DTO进行写入来实现读写分离(CQS?),两者都驻留在同一路径上,但使用不同的方法?
更新3:
- 将数据存储到其他持久层(ElasticSearch,MongoDB,外部Web服务......)
- 不公开通过API公开与数据库映射的内部模型
- 通过实现CQRS等模式,使用单独的模型进行读取操作和更新
是!我想要那个......但是如何在我的例子中实现它?
问题是Dto \ User \ CreateUser对象正在为响应进行序列化,实际上,您实际上希望返回并序列化Entity \ User。
当API平台序列化资源时,它们将为资源生成IRI。 IRI代是代码所在的地方。默认的IRI生成器使用Symfony路由器根据API平台创建的API路由实际构建路由。
因此,为了在实体上生成IRI,需要定义GET项操作,因为这是将成为资源的IRI的路由。
在您的情况下,DTO没有GET项操作(并且不应该有),但是当API平台尝试序列化您的DTO时,它会抛出该错误。
从您的代码示例中,看起来似乎正在返回User,但是,从错误中可以清楚地看出User实体不是被序列化的实体。
要做的一件事是安装debug-pack,使用bin/console server:dump
启动转储服务器,并在API平台WriteListener中添加一些转储语句:第53行附近的ApiPlatform \ Core \ EventListener \ WriteListener:
dump(["Controller Result: ", $controllerResult]);
$persistResult = $this->dataPersister->persist($controllerResult);
dump(["Persist Result: ", $persistResult]);
控制器结果应该是您的DTO的实例,持久结果应该是您的用户实体的实例,但我猜它正在返回您的DTO。
如果它返回你的DTO,你需要调试并找出为什么从dataPersister-> persist而不是User实体返回DTO。也许您的系统中存在可能导致冲突的其他数据持久性或事物。
希望这有帮助!
您需要在答案中发送“id”。
如果User是Doctrine实体,请使用:
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
如果User不是Doctrine实体,请使用:
/**
* @Assert\Type(type="integer")
* @ApiProperty(identifier=true)
*/
private $id;
无论如何,你的答案是这样的:
{
"id": 1, // Your unique id of User
"email": "[email protected]",
"plainPassword": "123qweQWE"
}
P.S。:对不起我的英文:)
仅适用于2.4版本,但真的很有帮助。
只需为CreateUserDTO添加output_class=false
,一切都可以用于POST | PUT | PATCH
output_class为false允许您绕过get项操作。你可以在ApiPlatform \ Core \ EventListener#L68中看到它。