I try to filter the collection returned by a parent Entity base on several conditions that refer to another Entity (User).
Basicaly i want API PLATFORM to return the messages from the current connected User only.
Here is my Entity named File (very simplified for global understanding)
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Serializer\Annotation\Groups;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter;
use App\Controller\FileCreatePdfController;
use Ramsey\Uuid\Uuid;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* #ApiResource(
*
* normalizationContext={"groups"={"file"},"enable_max_depth"=true},
* denormalizationContext={"groups"={"file-write"},"enable_max_depth"=true},
* attributes={"force_eager"=false},
* )
* #ApiFilter(SearchFilter::class, properties={"status": "exact"})
* #ApiFilter(DateFilter::class, properties={"updatedAt"})
* #ORM\Entity
* #ORM\Table(name="cases")
*/
class File
{
public function __construct()
{
$this->id = Uuid::uuid4();
$this->messages = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
/**
* #ORM\Id
* #ORM\Column(type="uuid", unique=true)
*/
private $id;
/**
* #var Collection|FileMessage[]
*
* #Groups({"file"})
* #ORM\OneToMany(targetEntity="App\Entity\FileMessage", mappedBy="file")
*/
private $messages;
/**
* #return Collection|FileMessage[]
*/
public function getMessages(): Collection
{
return $this->messages;
}
/**
* #param FileMessage $message
* #return File
*/
public function addMessage(FileMessage $message): self
{
if (false === $this->messages->contains($message)) {
$this->messages->add($message);
}
return $this;
}
}
My file contains some messages from FileMessage (very simplified for global understanding
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Ramsey\Uuid\Uuid;
/**
* #ApiResource(
* normalizationContext={"groups"={"file-message"}, "enable_max_depth"=true},
* denormalizationContext={"groups"={"file-message-write"}, "enable_max_depth"=true},
* attributes={"force_eager"=false}
* )
* #ORM\Entity
* #ORM\Table(name="`file_messages`")
*/
class FileMessage {
/**
*
* #ORM\Id
* #ORM\Column(type="uuid", unique=true)
*/
private $id;
/**
* #var File
*
* #ORM\ManyToOne(targetEntity="File", cascade={"persist"}, inversedBy="messages")
* #Assert\Valid
* #Groups({"file-message", "file-message-write","file"})
*/
private $file;
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="User", cascade={"persist"}, inversedBy="messages")
* #Assert\Valid
* #Groups({"file-message", "file-message-write","file"})
*/
private $user;
public function __construct()
{
$this->id = Uuid::uuid4();
}
public function getId()
{
return $this->id;
}
public function getFile(): ?File
{
return $this->file;
}
public function setFile(?File $file): self
{
$this->file = $file;
return $this;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
}
Each message is posted by a specific user (another entity User ) i don't think it is neccesary to post the content of this entity.
When i fetch a specific file , url/files/[file_id]
All the messages for all users are displayed , i want to hide all message that not refer to the connected User
Solution I have tried so far :
Extensions , I only filter the /files that i retreive not the messages collection
Custom filters , does not seems to be appropriated
Collection filtering , seems perfect only when you filter on current class properties , in my case i need to filter the properties based on another class content (User).
Do I have any solution ? I was thinking maybe to use an event listener
Extensions are the way to go. They give you access to the Doctrine ORM Query Builder, so you can filter both the current resource and its relations by tweaking the WHERE clause of the DQL query.
It's always better to filter the data as early as possible (for performance, among other reasons), so here doing it at the DBMS level is the best option.
Related
This is my first time posting, my apologies if I don't follow some rules.
I'm using API Platform in my Symfony 5.3 project. I'm trying to make a field in one of my entities writable with some rules. The entity is called StripeAccount and must be linked to a $company object (see mapping below). Here are the rules
If the user is NOT granted ROLE_ADMIN, then the $company is not mandatory as it will be automatically filled
If the user is NOT granted ROLE_ADMIN and provide the $company, it MUST match the user's one (or else a violation is added)
If the user IS granted ROLE_ADMIN, then the $company IS mandatory but it can be any company
This is my StripeAccount entity :
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Repository\StripeAccountRepository;
use App\Validator\IsValidCompany;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\MaxDepth;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #Vich\Uploadable
* #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"
* }
* )
* #ORM\Entity(repositoryClass=StripeAccountRepository::class)
*/
class StripeAccount
{
public const ACCOUNT_TYPE = 'custom';
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #Groups({"read:StripeAccount", "write:StripeAccount"})
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity=Company::class, inversedBy="stripeAccounts")
* #ORM\JoinColumn(nullable=false)
* #Groups({"read:StripeAccount", "admin:write"})
* #Assert\NotBlank(groups={"admin:write"})
* #IsValidCompany
*/
private $company;
/**
* #ORM\OneToMany(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
}
I followed this tutorial : https://symfonycasts.com/screencast/api-platform-security/context-builder#play (chapters 25, and 33 to 36), so I have this validator :
<?php
namespace App\Validator;
use App\Entity\{Company, User};
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class IsValidCompanyValidator extends ConstraintValidator
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function validate($value, Constraint $constraint)
{
/* #var $constraint \App\Validator\IsValidCompany */
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();
}
}
}
and this ContextBuilder :
<?php
namespace App\Serializer;
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
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;
}
}
Everything works fine, the group 'admin:write' is added if the user making the request is an admin, and the $company is set if the user is not an admin.
My problem is :
My #Assert\NotBlank(groups={"admin:write"}) is completly ignored. I tried a few adjustments with the #Groups annotation and even the denormalizationContext, but no, it's not applied at any moment. What am I missing here?
Btw, I'm using Postman to test my API
Thanks a lot for your help
[EDIT] Based on #TekPike's answer, here is my working code:
StripeAccount.php
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Repository\StripeAccountRepository;
use App\Validation\AdminValidationGroupsGenerator;
use App\Validator\IsValidCompany;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\MaxDepth;
use Symfony\Component\Validator\Constraints 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",
* }
* )
* #ORM\Entity(repositoryClass=StripeAccountRepository::class)
*/
class StripeAccount
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #Groups({"read:StripeAccount", "write:StripeAccount"})
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity=Company::class, inversedBy="stripeAccounts")
* #ORM\JoinColumn(nullable=false)
* #Groups({"read:StripeAccount", "admin:write"})
* #Assert\NotBlank(groups={"admin:write"})
* #IsValidCompany
*/
private $company;
/**
* #ORM\Column(type="string", length=255)
* #Groups({"read:StripeAccount", "write:StripeAccount"})
* #Assert\NotBlank
*/
private $name;
// ...
}
And my AdminValidationGroupsGenerator.php :
<?php
namespace App\Validation;
use ApiPlatform\Core\Bridge\Symfony\Validator\ValidationGroupsGeneratorInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
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];
}
}
You confuse serialization groups with validation groups.
Currently you define serialization groups with the annotation denormalizationContext={"groups"={"write:StripeAccount"}} and the class App\SerializerAdminGroupsContextBuilder.
However, the "admin:write" group defined in the constraint #Assert\NotBlank(groups={"admin:write"}) is a validation group.
In your case, since the validation group changes depending on the user, you have to use dynamic validation groups.
I have an Account entity which contains a collection of Group entities. In addition, each Account must have a default Group. How can one persist the entities? When attempting to do so, $manager->flush(); complains with a Not null violation for the entity that was persisted first.
Account:
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Bridge\Doctrine\IdGenerator\UuidV4Generator;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
*/
class Account
{
/**
* #ORM\Id
* #ORM\Column(type="uuid", unique=true)
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class=UuidV4Generator::class)
*/
private $id;
/**
* #ORM\OneToOne(targetEntity=Group::class, cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=false, unique=true)
*/
private $defaultGroup;
/**
* #ORM\OneToMany(targetEntity=Group::class, mappedBy="account")
*/
private $groups;
public function __construct()
{
$this->groups = new ArrayCollection();
$this->defaultGroup = new Group();
}
// Typical getters and setters
}
Group:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
*/
class Group
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity=Account::class, inversedBy="groups")
* #ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
private $account;
// Typical getters and setters
}
Your approach is not possible because Group::$account is already mapped to the one-to-many Account::$groups property. Doctrine does not know what to do when you persist a Group via Account::$defaultGroup, as this is not the property Group::$account is inversing.
You will have to enforce this Account default group invariant yourself programmatically. There's multiple approaches to this, but I suggest using the existing Account::$groups mapping in combination with the Symfony Collection validation constraint. Validating that an Account should have at least 1 Group assigned to it, which will be the default one.
Here's an example:
Because the entity name Group was giving me syntax problems in the SQL dialect I was using I renamed it to Team.
The Account implementation:
<?php declare(strict_types = 1);
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
*/
class Account
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* #ORM\OneToMany(
* targetEntity="App\Entity\Team",
* mappedBy="account",
* cascade={"persist", "remove"},
* )
* #Assert\Count(
* min=1
* )
*/
private Collection $teams;
public function __construct()
{
$this->teams = new ArrayCollection();
$this->addTeam(new Team());
}
public function getId(): ?int
{
return $this->id;
}
public function getDefaultTeam(): ?Team
{
$team = $this->teams->first();
return $team instanceof Team ? $team : null;
}
public function getTeams(): Collection
{
return $this->teams;
}
public function setTeams(array $teams): void
{
$this->teams->clear();
foreach ($teams as $team) {
if ($team instanceof Team) {
$this->addTeam($team);
}
}
}
public function addTeam(Team $team): void
{
if (false === $this->teams->contains($team)) {
$this->teams->add($team);
$team->setAccount($this);
}
}
public function removeTeam(Team $team): void
{
if (true === $this->teams->contains($team)) {
$this->teams->removeElement($team);
}
}
}
The Team implementation:
<?php declare(strict_types = 1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Team
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* #ORM\ManyToOne(
* targetEntity="App\Entity\Account",
* inversedBy="teams",
* )
* #ORM\JoinColumn(
* name="account_id",
* referencedColumnName="id",
* nullable=false
* )
*/
private ?Account $account = null;
public function getId(): ?int
{
return $this->id;
}
public function getAccount(): ?Account
{
return $this->account;
}
public function setAccount(Account $account): void
{
$this->account = $account;
}
}
I have a trouble with symfony.
I have three table in my database :
Musicien, Composer and Oeuvre
Composer is an association table between Musicien and Oeuvre.
When I load a page who show a Musicien, it show his Oeuvres.
So I make a join between Musicien and Composer, and between Composer and Oeuvre.
From a Musicien I can get all his composer with one request. But, to show his Oeuvres, I need to loop on his Composers to get all Oeuvres.
It makes one request by get, so if I have n Composer it will makes n request.
It is horribly slow.
I want to make it with one request, but I don't know how to do that, can you help me ?
Maybe if I make the join between Musicien and Oeuvre directly, but I don't know how to do that too.
Thank you.
This is my code :
Musicien:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Musicien
*
* #ORM\Table(name="Musicien", indexes={#ORM\Index(name="IDX_AC6BE67520B77BF2", columns={"Code_Pays"}), #ORM\Index(name="IDX_AC6BE675E1990660", columns={"Code_Genre"}), #ORM\Index(name="IDX_AC6BE675D389A975", columns={"Code_Instrument"})})
* #ORM\Entity
*/
class Musicien
{
/* Some attributes here*/
/**
* #var \Composer
*
* #ORM\OneToMany(targetEntity="Composer", mappedBy="codeMusicien")
*/
private $composer;
public function __construct(){
$this->oeuvres = new ArrayCollection();
}
public function getOeuvres()
{
$oeuvres = new ArrayCollection();
$oeuvres->add($this->composer->getCodeOeuvre());
// foreach($this->composer as $c){
// $oeuvres->add($c->getCodeOeuvre());
// }
return $oeuvres;
}
}
Composer
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Composer
*
* #ORM\Table(name="Composer", indexes={#ORM\Index(name="IDX_6105648EE694D5AB", columns={"Code_Musicien"}), #ORM\Index(name="IDX_6105648ECB48FCBD", columns={"Code_Oeuvre"})})
* #ORM\Entity
*/
class Composer
{
/**
* #var int
*
* #ORM\Column(name="Code_Composer", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $codeComposer;
/**
* #var \Musicien
*
* #ORM\ManyToOne(targetEntity="Musicien", inversedBy="composer")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="Code_Musicien", referencedColumnName="Code_Musicien")
* })
*/
private $codeMusicien;
/**
* #var \Oeuvre
*
* #ORM\ManyToOne(targetEntity="Oeuvre", inversedBy="composer")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="Code_Oeuvre", referencedColumnName="Code_Oeuvre")
* })
*/
private $codeOeuvre;
public function getCodeComposer(): ?int
{
return $this->codeComposer;
}
public function getCodeMusicien(): ?Musicien
{
return $this->codeMusicien;
}
public function setCodeMusicien(?Musicien $codeMusicien): self
{
$this->codeMusicien = $codeMusicien;
return $this;
}
public function getCodeOeuvre(): ?Oeuvre
{
return $this->codeOeuvre;
}
public function setCodeOeuvre(?Oeuvre $codeOeuvre): self
{
$this->codeOeuvre = $codeOeuvre;
return $this;
}
}
Oeuvre
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Oeuvre
*
* #ORM\Table(name="Oeuvre", indexes={#ORM\Index(name="IDX_32522BC898F61075", columns={"Code_Type"})})
* #ORM\Entity
*/
class Oeuvre
{
/**
* Some attributes here
*/
/**
* #var \Composer
*
* #ORM\OneToMany(targetEntity="Composer", mappedBy="codeOeuvre")
* #ORM\JoinTable(name="Composer")
*/
private $composer;
public function __construct(){
$this->musiciens = new ArrayCollection();
}
public function getMusiciens()
{
$musiciens = new ArrayCollection();
foreach($this->composer as $c){
$musiciens->add($c->getCodeMusicien());
}
return $musiciens;
}
}
I have custom action (saw in docs as recommended method) that makes some logic and returns doctrine collection of entities.
With regular api-platform action filters working perfectly. But how can i get any from default filters to work with this collection in my custom action?
When i request GET /cars?createdAt[after]=2018-08-01 or GET /drivers?createdAt[after]=2018-08-01 it works as expected.
But when i'm trying to do GET /drivers/42/cars_custom_logic?createdAt[after]=2018-08-01 it doesn't filter anything. It's expected as i didn't call filter in my custom action, but my question is – how to add this filter?
App\Entity\Car
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ORM\Entity
* #ApiResource
* #ApiFilter(DateFilter::class, properties={"createdAt"})
*/
class Car
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"car", "driver"})
*/
private $id;
/**
* #ORM\Column(type="datetime")
* #Groups({"car", "driver"})
*/
private $createdAt;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Driver", inversedBy="cars")
* #Groups({"car", "driver"})
*/
private $driver;
public function __construct()
{
$this->createdAt = new \DateTime('now');
}
public function getId(): int
{
return $this->id;
}
public function getCreatedAt(): \DateTimeInterface
{
return $this->createdAt;
}
public function getDriver(): Driver
{
return $this->driver;
}
}
App\Entity\Driver
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ORM\Entity
* #ApiResource(itemOperations={
* "get",
* "special"={
* "method"="GET",
* "path"="/drivers/{id}/cars_custom_logic",
* "controller"=GetDriverCarsAction::class
* }
* })
* #ApiFilter(DateFilter::class, properties={"createdAt"})
*/
class Driver
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"car", "driver"})
*/
private $id;
/**
* #ORM\Column(type="datetime")
* #Groups({"car", "driver"})
*/
private $createdAt;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Car", mappedBy="driver")
* #Groups({"car", "driver"})
*/
private $cars;
public function __construct()
{
$this->createdAt = new \DateTime('now');
}
public function getId(): int
{
return $this->id;
}
public function getCreatedAt(): \DateTimeInterface
{
return $this->createdAt;
}
/**
* #return Collection|Car[]
*/
public function getCars(): Collection
{
return $this->cars;
}
}
App\Controller\GetDriverCarsAction
<?php
namespace App\Controller;
use App\Entity\Car;
use App\Entity\Driver;
use Doctrine\Common\Collections\Collection;
use Symfony\Bridge\Doctrine\RegistryInterface;
final class GetDriverCarsAction
{
private $doctrine;
public function __construct(RegistryInterface $doctrine)
{
$this->doctrine = $doctrine;
}
public function __invoke(Driver $driver): Collection
{
$cars = $driver->getCars();
// ..... Some domain logic .....
// ..... Here – what should i do to make filter work here? .....
return $cars;
}
}
What if you try adding via yaml like this:
# api/config/api_platform/resources.yaml
App\Entity\Book:
attributes:
filters: [ offer.date_filter ]
itemOperations:
get: ~
special:
method: 'GET'
path: '/books/{id}/special'
controller: 'App\Controller\BookSpecial'
OR
# api/config/api_platform/resources.yaml
App\Entity\Book:
itemOperations:
get: ~
special:
method: 'GET'
path: '/books/{id}/special'
controller: 'App\Controller\BookSpecial'
filters: ['offer.date_filter']
For more deep look at this documentation: https://api-platform.com/docs/core/filters#doctrine-orm-filters
Hope it helps
I'm trying to set up my User entity to use roles, and following the documentation at http://symfony.com/doc/current/cookbook/security/entity_provider.html
Users work fine, and if I hardcode the $roles value everything works as expected, log in/out is good, etc. But, if I try and retrieve the roles through a many-to-many relationship as outlined in the documentation I get back null.
I should also mention that after creating the entities when I ran 'php app/console doctrine:schema:update --force' it created the role table, but not the 'user_role' table as it said it would. I went ahead and created it manually and entered a row for the user I was testing with, but that was my first clue something wasn't working. It's really frustrating because I followed the documentation and it looks like it should work.
The error I get back while trying to log-in is:
FatalErrorException: Error: Call to a member function toArray() on a non-object
Which points to return $this->roles->toArray() in the user entity.
My User Entity (the relevant bits):
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
/**
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="ACME\MyBundle\Entity\UserRepository")
*
*/
class User implements UserInterface, \Serializable
{
...
/**
* #ORM\ManyToMany(targetEntity="Role", inversedBy="users")
*
*/
private $roles;
...
/**
* Constructor
*/
public function __construct()
{
$this->roles = new ArrayCollection();
}
public function getRoles()
{
return $this->roles->toArray();
}
...
}
My Role Entity:
use Symfony\Component\Security\Core\Role\RoleInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="role")
* #ORM\Entity()
*/
class Role implements RoleInterface
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=30)
*/
private $name;
/**
* #ORM\Column(name="role", type="string", length=20, unique=true)
*/
private $role;
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="roles")
*/
private $users;
public function __construct()
{
$this->users = new ArrayCollection();
}
/**
* #see RoleInterface
*/
public function getRole()
{
return $this->role;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Role
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set role
*
* #param string $role
* #return Role
*/
public function setRole($role)
{
$this->role = $role;
return $this;
}
}
Does anybody see a problem in my code or have experience with this same issue? I'm stuck at the moment.
In your entity Role you have
* #ORM\Table(name="role")
change it to
* #ORM\Table(name="user_role")
because your table name is user_role not role
I had the same probleme and i removed the toArray methode
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
/**
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="ACME\MyBundle\Entity\UserRepository")
*
*/
class User implements UserInterface, \Serializable
{
...
/**
* #ORM\ManyToMany(targetEntity="Role", inversedBy="users")
*
*/
private $roles;
...
/**
* Constructor
*/
public function __construct()
{
$this->roles = new ArrayCollection();
}
public function getRoles()
{
return $this->roles;//return $this->roles->toArray();
}
...
}