I'm using API-Platform for a projetc. And in this project, I have two resources (Donateur and Don) releted by a ManyToOne relation. One Don is releted to One Donateur and One Donateur can be releted to more Dons. I have a data persister which persists resources in database.
Donateur.php
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Repository\DonateurRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ApiResource(
* normalizationContext={
* "groups"={
* "read:Donateur:collection"
* }
* },
* denormalizationContext={
* "groups"={
* "write:Donateur"
* }
* },
* collectionOperations={
* "get", "post"
* },
* itemOperations={
* "get", "patch", "delete"
* }
* )
* #ORM\Entity(repositoryClass=DonateurRepository::class)
*/
class Donateur
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #Groups("read:Donateur:collection")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Groups({"read:Donateur:collection", "write:Donateur"})
*/
private $nom;
/**
* #ORM\Column(type="float")
* #Groups({"read:Donateur:collection", "write:Don"})
*/
private $solde;
/**
* #ORM\OneToMany(targetEntity=Don::class, mappedBy="donateur")
*/
private $dons;
Don.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Repository\DonRepository;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ApiResource(
* normalizationContext={
* "groups"={
* "read:Don:item",
* "read:Don:collection"
* }
* },
* denormalizationContext={
* "groups"={
* "write:Don"
* }
* },
* collectionOperations={
* "get", "post"
* },
* itemOperations={
* "get"={
* "normalization_context"={
* "groups"={
* "read:Don:item",
* "read:Don:collection"
* }
* }
* },
* "patch", "delete"
* }
* )
* #ORM\Entity(repositoryClass=DonRepository::class)
*/
class Don
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity=Donateur::class, inversedBy="dons")
* #ORM\JoinColumn(nullable=false)
* #Groups({"read:Don:collection", "write:Don"})
*/
private $donateur;
/**
* #ORM\Column(type="date", nullable=true)
* #Groups({"read:Don:collection", "write:Don"})
*/
private $date;
/**
* #ORM\Column(type="float")
* #Groups({"read:Don:collection", "write:Don"})
*/
private $montant;
The property $solde in Donateur must increase when we save a new Don. And the value which must be added is value of $montant of Don resource.
When I try this code
<?php
namespace App\EventListener;
use ApiPlatform\Core\EventListener\EventPriorities;
use App\Entity\Don;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Security;
class UpdateSoldeDonateurSubscriber implements EventSubscriberInterface
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => ["updateSoldeDonateur", EventPriorities::POST_WRITE]
];
}
public function updateSoldeDonateur(ViewEvent $event)
{
$data = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
$user = $this->security->getUser();
//On va mettre à jour le solde du donateur. On récupère d'abord l'ancien solde puis on y ajoute le nouveau
if($data instanceof Don){
$ancienSolde = $data->getDonateur()->getSolde();
$nouveauSolde = $ancienSolde + $data->getMontant();
if($method === 'POST'){
$data->getDonateur()->setSolde($nouveauSolde);
}
elseif($method === 'PATCH'){
}
// dd($data->getDonateur(), $ancienSolde, $nouveauSolde);
}
}
}
solde didn't change. I think that it is caused by the data persister but I don't know how can I fix it.
These are my datapersisters
<?php
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use App\Entity\Donateur;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Security;
class DonateurDataPersister implements ContextAwareDataPersisterInterface
{
private $manager;
private $security;
public function __construct(EntityManagerInterface $manager, Security $security)
{
$this->manager = $manager;
$this->security = $security;
}
public function supports($data, array $context = []): bool
{
return $data instanceof Donateur;
}
public function persist($data, array $context = [])
{
$this->manager->persist($data);
$this->manager->flush();
return $data;
}
public function remove($data, array $context = [])
{
$data->setIsDeleted(true);
$data->setDeletedBy($this->security->getUser());
$data->setDeletedAt(new \DateTime());
$this->manager->flush();
}
}
<?php
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use App\Entity\Don;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Security;
class DonDataPersister implements ContextAwareDataPersisterInterface
{
private $manager;
private $security;
public function __construct(EntityManagerInterface $manager, Security $security)
{
$this->manager = $manager;
$this->security = $security;
}
public function supports($data, array $context = []): bool
{
return $data instanceof Don;
}
public function persist($data, array $context = [])
{
$this->manager->persist($data);
$this->manager->flush();
return $data;
}
public function remove($data, array $context = [])
{
$data->setIsDeleted(true);
$data->setDeletedBy($this->security->getUser());
$data->setDeletedAt(new \DateTime());
$this->manager->flush();
}
}
Some ideas to help me please
Your subscriber have been called after writing to the database and you should to flush after making changes in it or use another event that called before writing (PRE_WRITE, in example), as I mentioned in the comment.
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.
this is my repository, i wrote the function showActive() in there but when i try to call it on my controller it says that it is not defined.
<?php
namespace App\Repository;
use App\Entity\Pais;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* #method Pais|null find($id, $lockMode = null, $lockVersion = null)
* #method Pais|null findOneBy(array $criteria, array $orderBy = null)
* #method Pais[] findAll()
* #method Pais[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class PaisRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Pais::class);
}
/**
* #return pais[]
*/
public function showActive(){
$em = $this->getEntityManager();
$q = $em->createQuery('
SELECT pa
FROM App\Entity\Pais pa
WHERE pa.activo <= 1
');
}
heres my controller
<?php
namespace App\Controller;
use App\Entity\Pais;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class ListController extends AbstractController
{
/**
* #Route("/lista", name="lista")
*/
public function show(): Response {
$pais = $this->getDoctrine()->getRepository(Pais::class)->showActive();
return $this->render("main/lista.html.twig",array('paises'=>$pais));
}
}
am i missing something? i didn't find anything in the documentation
i tried to add use App\Repository\PaisRepository but it didn't fix it.
sorry for the probably dumb question.
Edit: here is my Pais entity.
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Pais
*
* #ORM\Table(name="pais", uniqueConstraints={#ORM\UniqueConstraint(name="abrev", columns={"abrev"})})
* #ORM\Entity(repositoryClass="App\Repository\PaisRepository")
*
*/
class Pais
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="descripcion", type="string", length=100, nullable=false)
*/
private $descripcion;
/**
* #var string
*
* #ORM\Column(name="abrev", type="string", length=10, nullable=false)
*/
private $abrev;
/**
* #var bool
*
* #ORM\Column(name="activo", type="boolean", nullable=false, options={"default"="1"})
*/
private $activo = true;
public function getId(): ?int
{
return $this->id;
}
public function getDescripcion(): ?string
{
return $this->descripcion;
}
public function setDescripcion(string $descripcion): self
{
$this->descripcion = $descripcion;
return $this;
}
public function getAbrev(): ?string
{
return $this->abrev;
}
public function setAbrev(string $abrev): self
{
$this->abrev = $abrev;
return $this;
}
public function getActivo(): ?bool
{
return $this->activo;
}
public function setActivo(bool $activo): self
{
$this->activo = $activo;
return $this;
}
}
Can you try to change your repository declaration in your entity as follow:
* #ORM\Entity(repositoryClass=App\Repository\PaisRepository::class)
In your entity:
<?php
namespace App\Entity;
use App\Repository\PaisRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* Pais
*
* #ORM\Table(name="pais", uniqueConstraints={#ORM\UniqueConstraint(name="abrev", columns={"abrev"})})
* #ORM\Entity(repositoryClass=PaisRepository::class)
*
*/
class Pais
And try to clear cache after that.
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 using EasyAdminBundle in Symfony 4 and I would like to create a multiple image upload in my form with VichUploaderBundle (or without), but I can't find any updated documentation for Symfony 4, I do not know what to do to make it work.
I created a Photo entity and a Product entity with the relationship many-to-many unidirectional :
Product.php :
<?php
declare(strict_types = 1);
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Class Product
*
* #package App\Entity
*
* #ORM\Entity
*/
class Product
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer", options={"unsigned":true}, nullable=false)
*
* #var int
*/
protected $id;
/**
* #ORM\ManyToMany(targetEntity="Photo", cascade={"persist"})
*
* #var Photo[]|ArrayCollection
*/
protected $photos;
/**
* Product constructor.
*/
public function __construct()
{
$this->photos = new ArrayCollection();
}
Photo.php :
<?php
declare(strict_types = 1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* Photo
*
* #ORM\Entity()
* #Vich\Uploadable
*/
class Photo
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*
* #var int
*/
protected $id;
/**
* #ORM\Column(name="name", type="string", length=255)
*
* #var string
*/
protected $name;
/**
* #ORM\Column(type="string", length=255)
*
* #var string
*/
protected $photo;
/**
* #Vich\UploadableField(mapping="photos", fileNameProperty="photo")
*
* #var File
*/
protected $photoFile;
/**
* #ORM\Column(type="datetime", length=255)
*
* #var \DateTime
*/
protected $updatedAt;
/**
* #return int
*/
public function getId(): ?int
{
return $this->id;
}
/**
* #param int $id
*/
public function setId(int $id): void
{
$this->id = $id;
}
/**
* #return string
*/
public function getName(): ?string
{
return $this->name;
}
/**
* #param string $name
*/
public function setName(string $name): void
{
$this->name = $name;
}
/**
* #param File|null $photo
* #return Photo
*/
public function setPhotoFile(File $photo = null)
{
$this->photoFile = $photo;
if ($photo) {
$this->updatedAt = new \DateTime('now');
}
return $this;
}
/**
* #return File
*/
public function getPhotoFile() : ?File
{
return $this->photoFile;
}
/**
* #param string $photo
* #return Photo
*/
public function setPhoto($photo)
{
$this->photo = $photo;
return $this;
}
/**
* #return string
*/
public function getPhoto() : ?string
{
return $this->photo;
}
}
The vich_uploader.yaml file :
vich_uploader:
db_driver: orm
mappings:
photos:
uri_prefix: "/products/"
upload_destination: '%kernel.root_dir%/../public/build/images/products/'
namer: vich_uploader.namer_uniqid
easy_admin.yaml :
easy_admin:
entities:
Product:
class: App\Entity\Product
form:
fields:
- { property: 'photos', type: 'collection', type_options: { entry_type: 'App\Form\PhotoType' } }
PhotoType.php :
<?php
declare(strict_types = 1);
namespace App\Form;
use App\Entity\Product;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichFileType;
/**
* Class PhotoType
*
* #package App\Form
*/
class PhotoType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('photos', CollectionType::class, ['entry_type' => VichFileType::class])
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Photo::class,
]);
}
}
A User has many Games.
A Game can Belong to many Users.
I have a form with a list of games, I want the list of Games to be added to the current logged User.
When I submit the form nothing happens, I want at least 1 record to be added to users_games:
Update:
Added User entity
FormType addGameToUserType:
namespace AppBundle\Form;
use AppBundle\Entity\Game;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
class addGameToUserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('game', EntityType::class, [
'class' => 'AppBundle:Game',
'choice_label' => function ($game) {
return $game->getName();
},
'multiple' => true,
'expanded' => false,
]);
}
public function configureOptions(OptionsResolver $resolver)
{
}
public function getBlockPrefix()
{
return 'app_bundleadd_game_to_user';
}
}
UserController addGameAction:
/**
* Adds game(s) to current user.
*
* #Route("user/game/add", name="game_add")
* #Method({"GET", "POST"})
*/
public function addGameAction(Request $request)
{
/** #var $form */
$form = $this->createForm('AppBundle\Form\addGameToUserType');
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
echo $form->get('game')->getData();
$em = $this->getDoctrine()->getManager();
/** #var $game */
$game = new Game();
$game->getId();
/** #var User $userObject */
$userObject = $this->getUser();
$user = $em->getRepository('AppBundle:User')
->find(['id' => $userObject->getId()]);
$game->addGameUser($user);
$em->persist($user);
$em->flush();
}
return $this->render('user/addGame.html.twig', array(
'form' => $form->createView()
));
}
Game entity:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Game
*
* #ORM\Table(name="game")
* #ORM\Entity(repositoryClass="AppBundle\Repository\GameRepository")
*/
class Game
{
/**
* #ORM\OneToMany(targetEntity="PlayLog", mappedBy="game")
* #ORM\OrderBy({"date" = "DESC"})
*
*/
private $playlogs;
private $users;
/**
* #return ArrayCollection
*/
public function getUsers()
{
return $this->users;
}
/**
* #param ArrayCollection $users
*/
public function setUsers($users)
{
$this->users = $users;
}
// private $categories;
public function __construct()
{
$this->playlogs = new ArrayCollection();
$this->users = new ArrayCollection();
}
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
* #Assert\NotBlank()
* #Assert\Length(
* min = "3",
* max = "100"
* )
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Game
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #return mixed
*/
public function getPlaylogs()
{
return $this->playlogs;
}
/**
* #param mixed $playlogs
*/
public function setPlaylogs($playlogs)
{
$this->playlogs = $playlogs;
}
public function addPlayLog(PlayLog $playlog)
{
$this->playlog->add($playlog);
$playlog->setPlayLogs($this);
}
public function addGameUser(User $user){
$this->users[] = $user;
}
}
User entity:
namespace AppBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\ManyToMany(targetEntity="Game")
*
* #ORM\JoinTable(name="users_games",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="game_id", referencedColumnName="id")}
* )
*/
private $games;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\PlayLog", mappedBy="user")
*
*/
private $playlogs;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #param mixed $id
*/
public function setId($id)
{
$this->id = $id;
}
public function __construct()
{
$this->games = new ArrayCollection();
}
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #return mixed
*/
public function getGames()
{
return $this->games;
}
/**
* #param mixed $games
*/
public function setGames($games)
{
$this->games = $games;
}
public function addGame(Game $game)
{
// $this->games->add($game);
$this->games[] = $game;
return $this;
}
public function removeGame(Game $game)
{
$this->games->removeElement($game);
}
/**
* #return mixed
*/
public function getPlaylogs()
{
return $this->playlogs;
}
/**
* #param mixed $playlogs
*/
public function setPlaylogs($playlogs)
{
$this->playlogs = $playlogs;
}
public function addPlayLog(PlayLog $playlog)
{
$this->playlog->add($playlog);
$playlog->setPlayLogs($this);
}
}