为admin创建一个字段@AssertNotBlank



这是我第一次发帖,如果我没有遵守一些规则,我很抱歉。

我在Symfony 5.3项目中使用API平台。我想让我的一个实体中的一个字段用一些规则可写。该实体名为StripeAccount,必须链接到$company对象(参见下面的映射)。以下是规则

  • 如果用户没有被授予ROLE_ADMIN,那么$company不是强制性的,因为它将自动填充
  • 如果用户没有被授予ROLE_ADMIN并且提供$company,它必须匹配用户的一个(否则添加违规)
  • 如果用户被授予ROLE_ADMIN,那么$company是强制性的,但它可以是任何公司

这是我的StripeAccount实体:

<?php
namespace AppEntity;
use ApiPlatformCoreAnnotationApiProperty;
use ApiPlatformCoreAnnotationApiResource;
use AppRepositoryStripeAccountRepository;
use AppValidatorIsValidCompany;
use DoctrineCommonCollectionsArrayCollection;
use DoctrineCommonCollectionsCollection;
use DoctrineORMMapping as ORM;
use SymfonyComponentHttpFoundationFileFile;
use SymfonyComponentSerializerAnnotationGroups;
use SymfonyComponentSerializerAnnotationMaxDepth;
use SymfonyComponentValidatorConstraints as Assert;
use VichUploaderBundleMappingAnnotation as Vich;
/**
* @VichUploadable
* @ApiResource(
*      iri="http://schema.org/StripeAccount",
*      normalizationContext={"groups"={"read:StripeAccount"}, "enable_max_depth"=true},
*      denormalizationContext={"groups"={"write:StripeAccount"}},
*      collectionOperations={
*          "post"={
*              "input_formats"={
*                  "multipart"={"multipart/form-data"}
*              },
*          },
*      },
*      itemOperations={
*          "get"
*      }
* )
* @ORMEntity(repositoryClass=StripeAccountRepository::class)
*/
class StripeAccount
{
public const ACCOUNT_TYPE = 'custom';
/**
* @ORMId
* @ORMGeneratedValue
* @ORMColumn(type="integer")
* @Groups({"read:StripeAccount", "write:StripeAccount"})
*/
private $id;
/**
* @ORMManyToOne(targetEntity=Company::class, inversedBy="stripeAccounts")
* @ORMJoinColumn(nullable=false)
* @Groups({"read:StripeAccount", "admin:write"})
* @AssertNotBlank(groups={"admin:write"})
* @IsValidCompany
*/
private $company;
/**
* @ORMOneToMany(targetEntity=Brand::class, mappedBy="stripeAccount")
* @Groups({"read:StripeAccount", "write:StripeAccount"})
*/
private $brands;
// other fields

public function __construct()
{
$this->brands = new ArrayCollection();
}
public static function getType(): string
{
return self::ACCOUNT_TYPE;
}
public function getId(): ?int
{
return $this->id;
}
public function getCompany(): ?Company
{
return $this->company;
}
public function setCompany(?Company $company): self
{
$this->company = $company;
return $this;
}
// other methods
}

我遵循本教程:https://symfonycasts.com/screencast/api-platform-security/context-builder#play(第25、33到36章),所以我有这个验证器:

<?php
namespace AppValidator;
use AppEntity{Company, User};
use SymfonyComponentSecurityCoreSecurity;
use SymfonyComponentValidatorConstraint;
use SymfonyComponentValidatorConstraintValidator;
class IsValidCompanyValidator extends ConstraintValidator
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function validate($value, Constraint $constraint)
{
/* @var $constraint AppValidatorIsValidCompany */
if (null === $value || '' === $value) {
return;
}
$user = $this->security->getUser();
if (!$user instanceof User) {
$this->context->buildViolation($constraint->anonymousMessage)->addViolation();
return;
}
if ($this->security->isGranted('ROLE_ADMIN')) {
return;
}
if (!$value instanceof Company) {
throw new InvalidArgumentException(
'@IsValidCompany constraint must be put on a property containing a Company object'
);
}
if ($value->getId() !== $user->getId()) {
$this->context->buildViolation($constraint->message)
->setParameter('%value%', $value)
->addViolation();
}
}
}

和这个ContextBuilder:

<?php
namespace AppSerializer;
use ApiPlatformCoreSerializerSerializerContextBuilderInterface;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentSecurityCoreAuthorizationAuthorizationCheckerInterface;
final class AdminGroupsContextBuilder implements SerializerContextBuilderInterface
{
private $decorated;
private $authorizationChecker;
public function __construct(
SerializerContextBuilderInterface $decorated,
AuthorizationCheckerInterface $authorizationChecker
) {
$this->decorated = $decorated;
$this->authorizationChecker = $authorizationChecker;
}
public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array
{
$context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);
$isAdmin = $this->authorizationChecker->isGranted('ROLE_ADMIN');
if (isset($context['groups']) && $isAdmin) {
$context['groups'][] = $normalization ? 'admin:read' : 'admin:write';
}
return $context;
}
}

一切正常,如果发出请求的用户是管理员,则添加组'admin:write',如果用户不是管理员,则设置$company。

我的问题是:我的@AssertNotBlank(groups={"admin:write"})被完全忽略。我尝试了一些调整@Groups注释,甚至denormalizationContext,但不,它没有在任何时候应用。我遗漏了什么?

顺便说一句,我使用邮差来测试我的API

谢谢你的帮助

[EDIT]基于@TekPike的回答,这是我的工作代码:

StripeAccount.php

<?php
namespace AppEntity;
use ApiPlatformCoreAnnotationApiProperty;
use ApiPlatformCoreAnnotationApiResource;
use AppRepositoryStripeAccountRepository;
use AppValidationAdminValidationGroupsGenerator;
use AppValidatorIsValidCompany;
use DoctrineCommonCollectionsArrayCollection;
use DoctrineCommonCollectionsCollection;
use DoctrineORMMapping as ORM;
use SymfonyComponentSerializerAnnotationGroups;
use SymfonyComponentSerializerAnnotationMaxDepth;
use SymfonyComponentValidatorConstraints as Assert;
/**
* @ApiResource(
*      iri="http://schema.org/StripeAccount",
*      attributes={
*          "validation_groups"=AdminValidationGroupsGenerator::class,
*      },
*      normalizationContext={"groups"={"read:StripeAccount"}, "enable_max_depth"=true},
*      denormalizationContext={"groups"={"write:StripeAccount"}},
*      collectionOperations={
*          "post"={
*              "input_formats"={
*                  "multipart"={"multipart/form-data"}
*              },
*          },
*      },
*      itemOperations={
*          "get",
*          "delete",
*      }
* )
* @ORMEntity(repositoryClass=StripeAccountRepository::class)
*/
class StripeAccount
{
/**
* @ORMId
* @ORMGeneratedValue
* @ORMColumn(type="integer")
* @Groups({"read:StripeAccount", "write:StripeAccount"})
*/
private $id;
/**
* @ORMManyToOne(targetEntity=Company::class, inversedBy="stripeAccounts")
* @ORMJoinColumn(nullable=false)
* @Groups({"read:StripeAccount", "admin:write"})
* @AssertNotBlank(groups={"admin:write"})
* @IsValidCompany
*/
private $company;

/**
* @ORMColumn(type="string", length=255)
* @Groups({"read:StripeAccount", "write:StripeAccount"})
* @AssertNotBlank
*/
private $name;
// ...
}

和AdminValidationGroupsGenerator.php:

<?php
namespace AppValidation;
use ApiPlatformCoreBridgeSymfonyValidatorValidationGroupsGeneratorInterface;
use SymfonyComponentSecurityCoreAuthorizationAuthorizationCheckerInterface;
final class AdminValidationGroupsGenerator implements ValidationGroupsGeneratorInterface
{
private $authorizationChecker;
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
}
/**
* {@inheritdoc}
*/
public function __invoke($entity): array
{
$reflect = new ReflectionClass($entity);
$name = "write:" . $reflect->getShortName();
return $this->authorizationChecker->isGranted('ROLE_ADMIN', $entity) ? [$name, 'admin:write'] : [$name];
}
}

混淆了序列化组验证组.

当前您使用注释denormalizationContext={"groups"={"write:StripeAccount"}}和类AppSerializerAdminGroupsContextBuilder定义序列化组。

但是,"admin:write"约束@AssertNotBlank(groups={"admin:write"})中定义的group为验证组。

在您的例子中,由于验证组根据用户而变化,因此您必须使用动态验证组。

最新更新