cascading manytomany relationship symfony insertion and edit - symfony-2.8

i'm here searching for your help, and i hope i can find some help
i have a cascading manytomany relationship, and i want to make insertion of objectif
i have manytomany between partenaire indicateur,
manytomany between indicateur and annee
and onetomany between annee objectif
which mean that a partenaire can have many objectif, each of those objectif bellong to an annee and an annee bellong to indicateur
here is my view on twig
<ul class="spancabine">
<div>
<label class="checkbox-inline">
{% for indicateur in indicateures %}
<li class="check-ser">
<label class="check">
<span><input name="indicateur[]"
onchange="change_checkbox(this)"
type="checkbox"
class="indicateur{{ indicateur.id }}"
value="{{ indicateur.id }}">{{ indicateur.titre }} </span>
</label>
</li>
</label>
</div>
<div>
<label class="checkbox-inline">
<div class="show{{ indicateur.id }}" style=" display: none; margin-left: 100px">
{% for annee in indicateur.annee %}
<li class="check-ser">
<label class="check">
<span><input type="checkbox" name="annee[]"
class="annee{{ annee.id }}"
alt="{{ annee.id }}{{ indicateur.id }}"
onchange="change_checkboxx(this)"
value="{{ annee.id }}">{{ annee.annee }} </span>
</label>
</li>
<li class="check-ser">
<label class="check">
<span>
<div class="object{{ annee.id }}{{ indicateur.id }}" style="display: none !important;">
<input class="object{{ annee.id }}"
type="text" name="objectif[]"
value="" style="">
</div> </span>
</label>
</li>
<br>
{% endfor %}
</div>
<br>
{% endfor %}
{#<input type="checkbox" value="">Indicateur 1#}
</label>
</div>
</ul>
and here is my action of insertion in my controller
public function AjouterPartenaireAction(Request $request) {
$partenaire = new Partenaire();
$form = $this->createForm('ApfeBundle\Form\PartenaireType', $partenaire);
$form->handleRequest($request);
$indicateures=$request->get('indicateur');
$annees=$request->get('annee');
$objectifs=$request->get('objectif');
$em = $this->getDoctrine()->getManager();
foreach ($indicateures as $indicateur) {
$indicateure = $em->getRepository('ApfeBundle:Indicateure')->findOneById($indicateur);
foreach ($annees as $annee) {
$annee = $em->getRepository('ApfeBundle:Annee')->findOneById($annee);
$annee->addIndicateure($indicateure);
$em->persist($annee);
$em->flush();
}
$partenaire->addIndicateure($indicateure);
}
$em->persist($partenaire);
$em->flush();
$mi = new \MultipleIterator();
$mi->attachIterator(new \ArrayIterator($annees));
$mi->attachIterator(new \ArrayIterator($objectifs));
$mi->attachIterator(new \ArrayIterator($indicateures));
foreach ($mi as $value) {
$annees = $value[0];
$objectif = $value[1];
$idindicateur = $value[2];
$em1 = $this->getDoctrine()->getManager();
$indicateure = $em->getRepository('ApfeBundle:Indicateure')->findOneById($idindicateur);
$annee = $em->getRepository('ApfeBundle:Annee')->findOneById($annees);
$obejctif = new Objectif();
$obejctif->setAnneeId($annee);
$obejctif->setObjectif($objectif);
$obejctif->setPartenaireId($partenaire);
$obejctif->setIndicateureId($indicateure);
$em1->persist($obejctif);
$em1->flush();
}
$form = $this->createForm(new PartenaireType(), $partenaire);
$form->handleRequest($request);
$em = $this->getDoctrine()->getManager();
$idpartenaire = $partenaire->getId();
$partenaires = $em->getRepository('ApfeBundle:Partenaire')->findAll();
return $this->container->get('templating')->renderResponse('partenaire/new.html.twig', array(
'idpartenaire' => $idpartenaire,
'partenaires' => $partenaires,
));
}
the probleme is when i submit it doesnt insert correctly all my entreies
as it appear in the pictures bellow
somebody can help please thank you

Ok. This will be a reeeealy long post. Sorry for that.
Here's how I replicated your problem.
First create the entities. I'm using yml for that, so if you use annotations, then it will be your job to convert yml style to annotation style.
//AppBundle/Resource/config/doctrine/Annee.orm.yml
AppBundle\Entity\Annee:
type: entity
table: null
repositoryClass: AppBundle\Repository\AnneeRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
manyToMany:
indicateurs:
targetEntity: AppBundle\Entity\Indicateur
mappedBy: annees
cascade: ['persist']
fetch: EAGER
oneToMany:
objectifes:
targetEntity: AppBundle\Entity\Objectif
mappedBy: annee
cascade: ['persist', 'remove']
orphanRemoval: true
nullable: true
fields:
name:
type: string
length: 255
lifecycleCallbacks: { }
//AppBundle/Resource/config/doctrine/Indicateur.orm.yml
AppBundle\Entity\Indicateur:
type: entity
table: null
repositoryClass: AppBundle\Repository\IndicateurRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
manyToMany:
annees:
targetEntity: AppBundle\Entity\Annee
inversedBy: indicateurs
joinTable:
name: annees_indicateurs
joinColumns:
indicateur_id:
referencedColumnName: id
inverseJoinColumns:
annee_id:
referencedColumnName: id
cascade: ['persist', 'remove']
fetch: EAGER
partenaires:
targetEntity: AppBundle\Entity\Partenaire
mappedBy: indicateurs
cascade: ['persist']
fetch: EAGER
fields:
name:
type: string
length: 255
lifecycleCallbacks: { }
//AppBundle/Resources/config/doctrine/Objectif.orm.yml
AppBundle\Entity\Objectif:
type: entity
table: null
repositoryClass: AppBundle\Repository\ObjectifRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
manyToOne:
annee:
targetEntity: AppBundle\Entity\Annee
inversedBy: objectifes
joinColumn:
name: annee_id
referencedColumnName: id
cascade: ['persist']
fields:
name:
type: string
length: 255
lifecycleCallbacks: { }
//AppBundle/Resources/config/doctrine/Partenaire.orm.yml
AppBundle\Entity\Partenaire:
type: entity
table: null
repositoryClass: AppBundle\Repository\PartenaireRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
manyToMany:
indicateurs:
targetEntity: AppBundle\Entity\Indicateur
inversedBy: partenaires
joinTable:
name: indicateurs_partenaires
joinColumns:
partenaire_id:
referencedColumnName: id
inverseJoinColumns:
indicateur_id:
referencedColumnName: id
cascade: ['persist', 'remove']
fetch: EAGER
fields:
name:
type: string
length: 255
lifecycleCallbacks: { }
Next step is to set up the doctrine's methods. Those will be used, internally, to take actions.
//AppBundle/Entity/Annee.php
//pay attention to the way addIndicateur(), removeIndicateur() methods are wrote. They are essential so that the many-to-many between Annee and Indicateur entities to work. The same for Indicateur.php and Partenaire.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Annee
*/
class Annee
{
/**
* #var int
*/
private $id;
/**
* #var string
*/
private $name;
/**
* #var \Doctrine\Common\Collections\Collection
*/
private $objectifes;
/**
* #var \Doctrine\Common\Collections\Collection
*/
private $indicateurs;
/**
* Constructor
*/
public function __construct()
{
$this->objectifes = new \Doctrine\Common\Collections\ArrayCollection();
$this->indicateurs = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Annee
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add objectife
*
* #param \AppBundle\Entity\Objectif $objectife
* #return Annee
*/
public function addObjectife(\AppBundle\Entity\Objectif $objectife)
{
$this->objectifes[] = $objectife;
return $this;
}
/**
* Remove objectife
*
* #param \AppBundle\Entity\Objectif $objectife
*/
public function removeObjectife(\AppBundle\Entity\Objectif $objectife)
{
$this->objectifes->removeElement($objectife);
}
/**
* Get objectifes
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getObjectifes()
{
return $this->objectifes;
}
/**
* Add indicateur
*
* #param \AppBundle\Entity\Indicateur $indicateur
* #return Annee
*/
public function addIndicateur(\AppBundle\Entity\Indicateur $indicateur)
{
if ($this->indicateurs->contains($indicateur)) {
return;
}
$this->indicateurs[] = $indicateur;
$indicateur->addAnnee($this);
return $this;
}
/**
* Remove indicateur
*
* #param \AppBundle\Entity\Indicateur $indicateur
*/
public function removeIndicateur(\AppBundle\Entity\Indicateur $indicateur)
{
if (!$this->indicateurs->contains($indicateur)) {
return;
}
$this->indicateurs->removeElement($indicateur);
$indicateur->removeAnnee($this);
}
/**
* Get indicateurs
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getIndicateurs()
{
return $this->indicateurs;
}
}
//AppBundle/Entity/Indicateur.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Indicateur
*/
class Indicateur
{
/**
* #var int
*/
private $id;
/**
* #var string
*/
private $name;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Indicateur
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #var \Doctrine\Common\Collections\Collection
*/
private $annees;
/**
* Constructor
*/
public function __construct()
{
$this->annees = new \Doctrine\Common\Collections\ArrayCollection();
$this->partenaires = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add annee
*
* #param \AppBundle\Entity\Annee $annee
* #return Indicateur
*/
public function addAnnee(\AppBundle\Entity\Annee $annee)
{
if ($this->annees->contains($annee)) {
return;
}
$this->annees[] = $annee;
$annee->addIndicateur($this);
return $this;
}
/**
* Remove annee
*
* #param \AppBundle\Entity\Annee $annee
*/
public function removeAnnee(\AppBundle\Entity\Annee $annee)
{
if (!$this->annees->contains($annee)) {
return;
}
$this->annees->removeElement($annee);
$annee->removeIndicateur($this);
}
/**
* Get annees
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getAnnees()
{
return $this->annees;
}
/**
* #var \Doctrine\Common\Collections\Collection
*/
private $partenaires;
/**
* Add partenaire
*
* #param \AppBundle\Entity\Partenaire $partenaire
* #return Indicateur
*/
public function addPartenaire(\AppBundle\Entity\Partenaire $partenaire)
{
if ($this->partenaires->contains($partenaire)) {
return;
}
$this->partenaires[] = $partenaire;
$partenaire->addIndicateur($this);
return $this;
}
/**
* Remove partenaire
*
* #param \AppBundle\Entity\Partenaire $partenaire
*/
public function removePartenaire(\AppBundle\Entity\Partenaire $partenaire)
{
if (!$this->partenaires->contains($partenaire)) {
return;
}
$this->partenaires->removeElement($partenaire);
$partenaire->removeIndicateur($this);
}
/**
* Get partenaires
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getPartenaires()
{
return $this->partenaires;
}
}
//AppBundle/Entity/Objectif.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Objectif
*/
class Objectif
{
/**
* #var int
*/
private $id;
/**
* #var string
*/
private $name;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Objectif
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #var \AppBundle\Entity\Annee
*/
private $annee;
/**
* Set annee
*
* #param \AppBundle\Entity\Annee $annee
* #return Objectif
*/
public function setAnnee(\AppBundle\Entity\Annee $annee = null)
{
$this->annee = $annee;
return $this;
}
/**
* Get annee
*
* #return \AppBundle\Entity\Annee
*/
public function getAnnee()
{
return $this->annee;
}
}
//AppBundle/Entity/Partenaire.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Partenaire
*/
class Partenaire
{
/**
* #var int
*/
private $id;
/**
* #var string
*/
private $name;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Partenaire
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #var \Doctrine\Common\Collections\Collection
*/
private $indicateurs;
/**
* Constructor
*/
public function __construct()
{
$this->indicateurs = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add indicateur
*
* #param \AppBundle\Entity\Indicateur $indicateur
* #return Partenaire
*/
public function addIndicateur(\AppBundle\Entity\Indicateur $indicateur)
{
if ($this->indicateurs->contains($indicateur)) {
return;
}
$this->indicateurs[] = $indicateur;
$indicateur->addPartenaire($this);
return $this;
}
/**
* Remove indicateur
*
* #param \AppBundle\Entity\Indicateur $indicateur
*/
public function removeIndicateur(\AppBundle\Entity\Indicateur $indicateur)
{
if (!$this->indicateurs->contains($indicateur)) {
return;
}
$this->indicateurs->removeElement($indicateur);
$indicateur->removePartenaire($this);
}
/**
* Get indicateurs
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getIndicateurs()
{
return $this->indicateurs;
}
}
Next, the forms (Type). I used php app/console doctrine:generate:crud for each entity, and I altered the resulted forms as:
//AppBundle/Form/AnneeType.php
<?php
namespace AppBundle\Form;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AnneeType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('indicateurs', EntityType::class, [
'class' => 'AppBundle:Indicateur',
'placeholder' => 'Choose an Indicateur',
'choice_label' => function($indicateurs) {
return $indicateurs->getName();
},
'multiple' => true,
'expanded' => false,
'by_reference' => false,
])
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Annee'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_annee';
}
}
//AppBundle/Form/IndicateurType.php
<?php
namespace AppBundle\Form;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class IndicateurType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('partenaires', EntityType::class, [
'class' => 'AppBundle:Partenaire',
'placeholder' => 'Choose a Partenaire',
'choice_label' => function($partenaire) {
return $partenaire->getName();
},
'multiple' => true,
'expanded' => false,
'by_reference' => false,
])
->add('annees', EntityType::class, [
'class' => 'AppBundle:Annee',
'placeholder' => 'Choose an Annee',
'choice_label' => function($annee) {
return $annee->getName();
},
'multiple' => true,
'expanded' => false,
'by_reference' => false,
])
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Indicateur'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_indicateur';
}
}
//AppBundle/Form/ObjectifType.php
<?php
namespace AppBundle\Form;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ObjectifType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('annee', EntityType::class, [
'class' => 'AppBundle:Annee',
'placeholder' => 'Select Annee',
'choice_label' => function($annee) {
return $annee->getName();
},
'multiple' => false,
'expanded' => false
])
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Objectif'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_objectif';
}
}
//AppBundle/Form/PartenaireType.php
<?php
namespace AppBundle\Form;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PartenaireType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('indicateurs', EntityType::class, [
'class' => 'AppBundle:Indicateur',
'placeholder' => 'Choose an Indicateur',
'choice_label' => function($indicateur) {
return $indicateur->getName();
},
'multiple' => true,
'expanded' => false,
'by_reference' => false,
])
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Partenaire'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_partenaire';
}
}
For rendering, I used the default crud forms, with the addition of novalidate attribute (for all of the forms):
{{ form_start(form,{attr:{'novalidate':'novalidate'}}) }}
I used the default CRUD controllers, and routes. If you are using, like me, yaml when creating the CRUD for each entity, then you'll need to import the main routing.yml file into app/config/routing.yml:
crud:
resource: '#AppBundle/Resources/config/routing.yml'
otherwise, if you're using annotations, you're all set.

Related

Encode password easyadmin v3

I have my user that I can manage from my administration panel, I can change the password, but the problem is that in the database it is not encrypted. It is in clear in the database, Save you how I could do it so that it is not anymore? I give you my user entity as well as the crud user And I use the easyadmin v3 and symfony 5 bundle.
My entity User
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
*/
class User implements UserInterface
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
private $email;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string")
*/
private $password;
/**
* #ORM\Column(type="string", length=255)
*/
private $prenom;
/**
* #ORM\Column(type="string", length=255)
*/
private $nom;
/**
* #ORM\Column(type="string", length=255)
*/
private $telephone;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $aPropos;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $facebook;
/**
* #ORM\OneToMany(targetEntity=Realisation::class, mappedBy="user", orphanRemoval=true)
*/
private $realisations;
public function __construct()
{
$this->realisations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUsername(): string
{
return (string) $this->email;
}
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* #see UserInterface
*/
public function getPassword(): string
{
return (string) $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* Returning a salt is only needed, if you are not using a modern
* hashing algorithm (e.g. bcrypt or sodium) in your security.yaml.
*
* #see UserInterface
*/
public function getSalt(): ?string
{
return null;
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function getPrenom(): ?string
{
return $this->prenom;
}
public function setPrenom(string $prenom): self
{
$this->prenom = $prenom;
return $this;
}
public function getNom(): ?string
{
return $this->nom;
}
public function setNom(string $nom): self
{
$this->nom = $nom;
return $this;
}
public function getTelephone(): ?string
{
return $this->telephone;
}
public function setTelephone(string $telephone): self
{
$this->telephone = $telephone;
return $this;
}
public function getAPropos(): ?string
{
return $this->aPropos;
}
public function setAPropos(?string $aPropos): self
{
$this->aPropos = $aPropos;
return $this;
}
public function getFacebook(): ?string
{
return $this->facebook;
}
public function setFacebook(?string $facebook): self
{
$this->facebook = $facebook;
return $this;
}
/**
* #return Collection|Realisation[]
*/
public function getRealisations(): Collection
{
return $this->realisations;
}
public function addRealisation(Realisation $realisation): self
{
if (!$this->realisations->contains($realisation)) {
$this->realisations[] = $realisation;
$realisation->setUser($this);
}
return $this;
}
public function removeRealisation(Realisation $realisation): self
{
if ($this->realisations->removeElement($realisation)) {
// set the owning side to null (unless already changed)
if ($realisation->getUser() === $this) {
$realisation->setUser(null);
}
}
return $this;
}
public function __toString()
{
return $this->nom;
}
/* public function __toString(){
return $this->nom;
}*/
}
<?php
namespace App\Controller\Admin;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
class UserCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return User::class;
}
public function configureFields(string $pageName): iterable
{
return [
IntegerField::new('id','ID')->onlyOnIndex(),
TextField::new('email'),
TextField::new('password'),
TextField::new('nom'),
TextField::new('telephone'),
TextField::new('aPropos'),
TextField::new('facebook'),
];
}
}
This could be helpful ...
<?php
namespace App\Event\Subscriber;
use App\Entity\BackendUser;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityUpdatedEvent;
class EasyAdminHooksSubscriber implements EventSubscriberInterface {
/**
* #var UserPasswordEncoderInterface
*/
private $passwordEncoder;
/**
* #var ContainerInterface
*/
private $container;
/**
* EasyAdminSubscriber constructor.
*
* #param UserPasswordEncoderInterface $passwordEncoder
* #param ContainerInterface $container
*/
public function __construct(UserPasswordEncoderInterface $passwordEncoder, ContainerInterface $container) {
$this->passwordEncoder = $passwordEncoder;
$this->container = $container;
}
public static function getSubscribedEvents(): array {
return array(
BeforeEntityUpdatedEvent::class => array('preUpdateEntity')
);
}
/**
* #param BeforeEntityUpdatedEvent $event
*
* #noinspection PhpUnused
*/
public function preUpdateEntity(BeforeEntityUpdatedEvent $event) {
$entity = $event->getEntityInstance();
if($entity instanceof BackendUser) {
$this->preUpdateBackendUser($entity);
}
}
/**
* #param BackendUser $be_user
*/
private function preUpdateBackendUser(BackendUser &$be_user) {
$plain_password = $be_user->getPlainPassword();
if(!empty($plain_password)) {
$new_password = $this->passwordEncoder->encodePassword($be_user, $plain_password);
$be_user->setPassword($new_password);
$be_user->setPlainPassword();
}
}
}
Here's a solution I found while trying to create/edit users from my web app's admin dashboard (Symfony 5.3 and EasyAdmin v3). I found it in the EasyAdmin issue tracker over on Github.
You will need to add a plain password field to you User class and set the appropriate getter & setter methods.
/**
* #var string
*/
private $plainPassword;
/**
* #return string
*/
public function getPlainPassword(): string
{
return $this->plainPassword;
}
Add event listeners to listen to create/edit form submission events, extract the plain password from the submitted form data and then hash it.
/** #var UserPasswordHasherInterface */
private $hasher;
public function createEditFormBuilder(EntityDto $entityDto, KeyValueStore $keyValueStore, AdminContext $context): FormBuilderInterface
{
$formBuilder = parent::createEditFormBuilder($entityDto, $keyValueStore, $context);
$this->addEncodePasswordEventListener($formBuilder);
return $formBuilder;
}
public function createNewFormBuilder(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormBuilderInterface
{
$formBuilder = parent::createNewFormBuilder($entityDto, $formOptions, $context);
$this->addEncodePasswordEventListener($formBuilder);
return $formBuilder;
}
/**
* #param FormBuilderInterface $formBuilder
*/
public function addEncodePasswordEventListener(FormBuilderInterface $formBuilder)
{
$formBuilder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event){
/** #var User $user */
$user = $event->getData();
if ($user->getPlainPassword()) {
$user->setPassword($this->hasher->hashPassword($user, $user->getPlainPassword()));
}
});
}
Then finally you need to render the appropriate form fields.
public function configureFields(string $pageName): iterable
{
return [
# other fields
Field::new('plainPassword', 'New Password')->onlyOnForms()
->setFormType(RepeatedType::class)
->setFormTypeOptions([
'type' => PasswordType::class,
'first_options' => ['label' => 'New password'],
'second_options' => ['label' => 'Repeat Password']
])->setRequired(true)
];
}
Hope someone finds this useful.
You can add plain password field in User entity:
private ?string $plainPassword= '';
public function getPlainPassword(): ?string
{
return $this->plainPassword;
}
public function setPlainPassword(?string $plainPassword): void
{
$this->plainPassword = $plainPassword;
}
Add password updater in User repo:
public function setNewPassword(PasswordAuthenticatedUserInterface $user, string $plainPassword): void
{
$hashedPassword = $this->hasher->hashPassword($user, $plainPassword);
$user->setPassword($hashedPassword);
$this->_em->persist($user);
$this->_em->flush();
}
And override update/persist methods In UserCrudController:
public function updateEntity(EntityManagerInterface $entityManager, $entityInstance): void
{
$this->updatePassword($entityInstance);
parent::updateEntity($entityManager, $entityInstance);
}
public function persistEntity(EntityManagerInterface $entityManager, $entityInstance): void
{
$this->updatePassword($entityInstance);
parent::persistEntity($entityManager, $entityInstance);
}
private function updatePassword(User $user): void
{
if ($user->getPlainPassword() == '') return;
$this->userRepository->setNewPassword($user, $user->getPlainPassword());
}
It's work in Symfony 5.4.2 / EasyAdmin 3.5.19

Symfony 4 JWT : I can't get response from controller when accessing api

I'm working on this tutorial : Implementing JWT Authentication to your API Platform application
and I'm trying to get a protected access to the api action controller :
public function api()
{
return new Response(sprintf('Logged in as %s', $this->getUser()->getUsername()));
}
For reminder here is the security.yaml :
security:
encoders:
App\Entity\User:
algorithm: bcrypt
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
entity_provider:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
# main:
# anonymous: true
login:
pattern: ^/login
stateless: true
anonymous: true
json_login:
check_path: /login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
register:
pattern: ^/register
stateless: true
anonymous: true
api:
pattern: ^/api
stateless: true
anonymous: false
provider: entity_provider
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
# activate different ways to authenticate
# http_basic: true
# https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
# form_login: true
# https://symfony.com/doc/current/security/form_login_setup.html
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
And the routes.yaml :
register:
path: /register
controller: App\Controller\AuthController::register
methods: POST
api:
path: /api
controller: App\Controller\AuthController::api
login_check:
path: /login_check
methods: [POST]
I have the same code excepted the User entity :
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #ApiResource(normalizationContext={"groups"={"user"}})
* #ApiFilter(SearchFilter::class, properties={"centres.id": "exact"})
*/
class User implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"user"})
*/
private $id;
/**
* #ORM\Column(type="string", length=50, unique=true)
* #Groups({"user"})
*/
private $username;
/**
* #ORM\Column(type="string", length=64)
* #Groups({"user"})
*/
private $password;
/**
* #ORM\Column(type="string", length=50, nullable=true)
* #Groups({"user"})
*/
private $prenom;
/**
* #ORM\Column(type="string", length=50, nullable=true)
* #Groups({"user"})
*/
private $nom;
/**
* #ORM\Column(type="string", length=80, unique=true)
* #Groups({"user"})
*/
private $email;
/**
* #ORM\Column(type="array")
* #Groups({"user"})
*/
private $roles = [];
/**
* #ORM\Column(type="datetime", nullable=true)
* #Groups({"user"})
*/
private $dateNaissance;
/**
* #ORM\Column(type="datetime")
* #Groups({"user"})
*/
private $dateEnregistrement;
/**
* #ORM\Column(type="datetime", nullable=true)
* #Groups({"user"})
*/
private $dateDernierePartie;
/**
* #ORM\Column(type="boolean")
* #Groups({"user"})
*/
private $actif;
/**
* #ORM\Column(type="integer")
* #Groups({"user"})
*/
private $niveau;
/**
* #ORM\Column(type="integer")
* #Groups({"user"})
*/
private $experience;
/**
* #ORM\Column(type="integer")
* #Groups({"user"})
*/
private $nbVictimes;
/**
* #ORM\Column(type="integer")
* #Groups({"user"})
*/
private $nbMorts;
/**
* #ORM\Column(type="integer", nullable=true)
* #Groups({"user"})
*/
private $justesse;
/**
* #ORM\Column(type="integer", nullable=true)
* #Groups({"user"})
*/
private $nbParties;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Carte", mappedBy="client")
* #Groups({"user"})
* #var Collection
*/
private $cartes;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Equipe", inversedBy="joueurs")
* #ORM\JoinColumn(nullable=true)
* #Groups({"user"})
*/
private $equipe;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Centre", inversedBy="clients")
* #ORM\JoinTable(name="users_centres")
* #var Collection
* #Groups({"user"})
*/
private $centres;
public function __construct()
{
$this->cartes = new ArrayCollection();
$this->centres = new ArrayCollection();
$this->actif = true;
$this->niveau = 1;
$this->experience = 0;
$this->nbVictimes = 0;
$this->nbMorts = 0;
$this->justesse = 0;
$this->nbParties = 0;
$this->dateEnregistrement = new \DateTime();
}
/**
* #param int|null $id
* #param string $username
* #param string $email
* #param string $password
* #param array $roles
* #param \DateTime|null $dateEnregistrement
* #return User
*/
static public function creer(
?int $id = null,
string $username,
string $email,
string $password,
array $roles,
?\DateTime $dateEnregistrement = null
)
{
$user = new self();
$user->id = $id;
$user->username = $username;
$user->email = $email;
$user->password = $password;
$user->roles = $roles;
$user->dateEnregistrement = $dateEnregistrement;
return $user;
}
public function addCarte(Carte $carte)
{
if ($this->cartes->contains($carte)) {
return;
}
$this->cartes->add($carte);
$carte->setClient($this);
}
public function addCentre(Centre $centre)
{
if ($this->centres->contains($centre)) {
return;
}
$this->centres->add($centre);
//$centre->inscrireJoueur($this);
}
public function ajouterNbVictimes(int $nbVictimes)
{
$this->nbVictimes += $nbVictimes;
}
public function ajouterJustesse(int $justesse)
{
$this->justesse += $justesse;
}
public function diminuerJustesse(int $justesse)
{
$this->justesse -= $justesse;
}
public function ajouterNbMorts(int $nbMorts)
{
$this->nbMorts += $nbMorts;
}
public function getId(): ?int
{
return $this->id;
}
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
public function getPassword(): ?string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
public function getPrenom(): ?string
{
return $this->prenom;
}
public function setPrenom(string $prenom): self
{
$this->prenom = $prenom;
return $this;
}
public function getNom(): ?string
{
return $this->nom;
}
public function setNom(string $nom): self
{
$this->nom = $nom;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function getRoles(): ?array
{
return $this->roles;
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
public function getDateNaissance(): ?\DateTimeInterface
{
return $this->dateNaissance;
}
public function setDateNaissance(\DateTimeInterface $dateNaissance): self
{
$this->dateNaissance = $dateNaissance;
return $this;
}
public function getDateEnregistrement(): ?\DateTimeInterface
{
return $this->dateEnregistrement;
}
public function setDateEnregistrement(\DateTimeInterface $dateEnregistrement): self
{
$this->dateEnregistrement = $dateEnregistrement;
return $this;
}
public function getDateDernierePartie(): ?\DateTimeInterface
{
return $this->dateDernierePartie;
}
public function setDateDernierePartie(?\DateTimeInterface $dateDernierePartie): self
{
$this->dateDernierePartie = $dateDernierePartie;
return $this;
}
public function getActif(): ?bool
{
return $this->actif;
}
public function setActif(bool $actif): self
{
$this->actif = $actif;
return $this;
}
public function getNiveau(): ?int
{
return $this->niveau;
}
public function setNiveau(int $niveau): self
{
$this->niveau = $niveau;
return $this;
}
public function getExperience(): ?int
{
return $this->experience;
}
public function setExperience(int $experience): self
{
$this->experience = $experience;
return $this;
}
public function getNbVictimes(): ?int
{
return $this->nbVictimes;
}
public function setNbVictimes(int $nbVictimes): self
{
$this->nbVictimes = $nbVictimes;
return $this;
}
public function getNbMorts(): ?int
{
return $this->nbMorts;
}
public function setNbMorts(int $nbMorts): self
{
$this->nbMorts = $nbMorts;
return $this;
}
public function getJustesse(): ?int
{
return $this->justesse;
}
public function setJustesse(int $justesse): self
{
$this->justesse = $justesse;
return $this;
}
/**
* #return mixed
*/
public function getNbParties()
{
return $this->nbParties;
}
/**
* #param mixed $nbParties
*/
public function setNbParties($nbParties): void
{
$this->nbParties = $nbParties;
}
/**
* #return mixed
*/
public function getCartes()
{
return $this->cartes;
}
/**
* #param mixed $cartes
*/
public function setCartes($cartes): void
{
$this->cartes = $cartes;
}
/**
* #return mixed
*/
public function getEquipe()
{
return $this->equipe;
}
/**
* #param mixed $equipe
*/
public function setEquipe($equipe): void
{
$this->equipe = $equipe;
}
/**
* #return mixed
*/
public function getCentres()
{
return $this->centres;
}
/**
* #param mixed $centre
*/
public function setCentres($centres): void
{
$this->centres = $centres;
}
/**
* Returns the salt that was originally used to encode the password.
*
* This can return null if the password was not encoded using a salt.
*
* #return string|null The salt
*/
public function getSalt()
{
return null;
}
/**
* Returns the username used to authenticate the user.
*
* #return string The username
*/
public function getUsername()
{
return $this->username;
}
/**
* Removes sensitive data from the user.
*
* This is important if, at any given point, sensitive information like
* the plain-text password is stored on this object.
*/
public function eraseCredentials()
{
}
}
I have also a Carte entity, a Centre entity, an Equipe entity and a Partie entity.
I'm using curl or Postamn to make requests :
I've made curl -H "Authorization: Bearer [TOKEN]" http://localhost:8000/api but the result is :
{"#context":"\/api\/contexts\/Entrypoint","#id":"\/api","#type":"Entrypoint","user":"\/api\/users","carte":"\/api\/cartes","equipe":"\/api\/equipes","centre":"\/api\/centres","partie":"\/api\/parties"}
Or with Postman :
{
"#context": "/api/contexts/Entrypoint",
"#id": "/api",
"#type": "Entrypoint",
"user": "/api/users",
"carte": "/api/cartes",
"equipe": "/api/equipes",
"centre": "/api/centres",
"partie": "/api/parties"
}
I don't get the message Logged in as [username] as expected.
How to get it ?
Thanks for help.
I had this same issue and to solve this I had to comment/remove this line from this file: config/routes/api_platform.yaml
api_platform:
resource: .
type: api_platform
# prefix: /api

Symfony API with FOSRestBundle : circular reference has been detected

I'm working on a symfony project to build a rest API, I have 4 entities related to each other like that :
I've installed FOSRestBundle to build just a GET web service, when i want to get a resource for example :
http://evaluation.dev/app_dev.php/api/families
I got this error :
message: "A circular reference has been detected (configured
limit:1).",
this is my controller :
<?php
namespace API\APIBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\Annotations as Rest;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Serializer;
class CartographyController extends Controller
{
/**
* #Rest\View()
* #Rest\Get("/posts")
* #param Request $request
* #return Response
*/
public function getPostsAction(Request $request)
{
$encoder = new JsonEncoder();
$normalizer = new GetSetMethodNormalizer();
$serializer = new Serializer(array($normalizer), array($encoder));
$posts = $this->get('doctrine.orm.entity_manager')
->getRepository('EvalBundle:Post')
->findAll();
return new Response($serializer->serialize($posts, 'json'));
}
/**
* #Rest\View()
* #Rest\Get("/employments")
* #param Request $request
* #return Response
*/
public function geEmploymentAction(Request $request)
{
$encoder = new JsonEncoder();
$normalizer = new GetSetMethodNormalizer();
$serializer = new Serializer(array($normalizer), array($encoder));
$employments = $this->get('doctrine.orm.entity_manager')
->getRepository('EvalBundle:Employment')
->findAll();
return new Response($serializer->serialize($employments, 'json'));
}
/**
* #Rest\View()
* #Rest\Get("/professions")
* #param Request $request
* #return Response
*/
public function geProfessionsAction(Request $request)
{
$encoder = new JsonEncoder();
$normalizer = new GetSetMethodNormalizer();
$serializer = new Serializer(array($normalizer), array($encoder));
$professions = $this->get('doctrine.orm.entity_manager')
->getRepository('EvalBundle:Profession')
->findAll();
return new Response($serializer->serialize($professions, 'json')); }
/**
* #Rest\View()
* #Rest\Get("/families")
* #param Request $request
* #return Response
*/
public function geFamiliesAction(Request $request)
{
$encoder = new JsonEncoder();
$normalizer = new GetSetMethodNormalizer();
$serializer = new Serializer(array($normalizer), array($encoder));
$families = $this->get('doctrine.orm.entity_manager')
->getRepository('EvalBundle:Family')
->findAll();
return new Response($serializer->serialize($families, 'json'));
}
}
Family entity :
<?php
namespace EvalBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* Family
*
* #ORM\Table(name="family")
* #ORM\Entity(repositoryClass="EvalBundle\Repository\FamilyRepository")
*/
class Family
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* One Family has Many Professions.
* #ORM\OneToMany(targetEntity="Profession", mappedBy="family",orphanRemoval=true,cascade={"persist", "remove"},fetch="EAGER")
*/
protected $professions;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Family
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #return mixed
*/
public function getProfessions()
{
return $this->professions;
}
/**
* #param mixed $professions
*/
public function setProfessions($professions)
{
$this->professions = $professions;
}
public function __toString() {
return $this->name;
}
}
Profession entity :
<?php
namespace EvalBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Profession
*
* #ORM\Table(name="profession")
* #ORM\Entity(repositoryClass="EvalBundle\Repository\ProfessionRepository")
*/
class Profession
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* Many professions have One Family.
* #ORM\ManyToOne(targetEntity="Family", inversedBy="professions",cascade={"persist", "remove"})
*/
public $family;
/**
* One Profession has Many Employment.
* #ORM\OneToMany(targetEntity="Employment", mappedBy="profession",cascade={"persist", "remove"}, orphanRemoval=true,fetch="EAGER")
*/
private $employments;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Profession
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #return mixed
*/
public function getEmployments()
{
return $this->employments;
}
/**
* #param mixed $employments
*/
public function setEmployments($employments)
{
$this->employments = $employments;
}
/**
* #return mixed
*/
public function getFamily()
{
return $this->family;
}
/**
* #param mixed $family
*/
public function setFamily($family)
{
$this->family = $family;
}
public function __toString() {
return $this->name;
}
}
Post entity
<?php
namespace EvalBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Post
*
* #ORM\Table(name="post")
* #ORM\Entity(repositoryClass="EvalBundle\Repository\PostRepository")
*/
class Post
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* Many Posts have One Employment.
* #ORM\ManyToOne(targetEntity="Employment", inversedBy="posts", fetch="EAGER")
* #ORM\JoinColumn(name="employment_id", referencedColumnName="id",nullable=false)
*/
public $employment;
/**
* One Post has Many LevelRequired.
* #ORM\OneToMany(targetEntity="RequiredLevel", mappedBy="post")
*/
private $requiredLevels;
/**
* One Post has Many PostEvaluation.
* #ORM\OneToMany(targetEntity="PostEvaluation", mappedBy="post")
*/
private $postEvaluations;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Post
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #return mixed
*/
public function getEmployment()
{
return $this->employment;
}
public function getProfession(){
return $this->employment->profession;
}
public function getFamily(){
return $this->employment->profession->family;
}
/**
* #param mixed $employment
*/
public function setEmployment($employment)
{
$this->employment = $employment;
}
public function __toString() {
return $this->name;
}
}
employment entity
<?php
namespace EvalBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Employment
*
* #ORM\Table(name="employment")
* #ORM\Entity(repositoryClass="EvalBundle\Repository\EmploymentRepository")
*/
class Employment
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* Many Employments have One Profession.
* #ORM\ManyToOne(targetEntity="Profession", inversedBy="employments",fetch="EAGER")
*/
public $profession;
/**
* One Employment has Many post.
* #ORM\OneToMany(targetEntity="Post", mappedBy="employment",cascade={"persist", "remove"}, orphanRemoval=true,cascade={"persist", "remove"})
*/
private $posts;
/**
* One Employment has One Grid.
* #ORM\OneToOne(targetEntity="Grid", mappedBy="employment")
*/
private $grid;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Employment
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #return mixed
*/
public function getProfession()
{
return $this->profession;
}
/**
* #param mixed $profession
*/
public function setProfession($profession)
{
$this->profession = $profession;
}
/**
* #return mixed
*/
public function getPosts()
{
return $this->posts;
}
/**
* #param mixed $posts
*/
public function setPosts($posts)
{
$this->posts = $posts;
}
/**
* #return mixed
*/
public function getGrid()
{
return $this->grid;
}
/**
* #param mixed $grid
*/
public function setGrid($grid)
{
$this->grid = $grid;
}
public function __toString() {
return $this->name;
}
}
any solution please ?
For you example, you can avoid the CircularReference like this
$normalizer = new ObjectNormalizer();
$normalizer->setCircularReferenceLimit(1);
$normalizer->setCircularReferenceHandler(function ($object) {
return $object->getName();
});
$serializer = new Serializer(array($normalizer), array(new JsonEncoder()));
var_dump($serializer->serialize($org, 'json'));
But in your example, you don't use the FOSRestBundle for the view in your controller. The FOSRestController give you a handleView() and a view() method. Like this
class CartographyController extends FOSRestController
{
public function getPostsAction(Request $request)
{
$posts = $this->get('doctrine.orm.entity_manager')
->getRepository('EvalBundle:Post')
->findAll();
$view = $this->view($posts, 200);
return $this->handleView($view);
}
In this case, the serialiser is a service, this service is activated in config.yml :
In your app/config/config.yml
framework:
serializer: { enabled: true }
In order to avoid circularReference, you can do this.
In your app/config/services.yml
circular_reference_handler:
public: false
class: callback
factory: [AppBundle\Serializer\CircularHandlerFactory, getId]
serializer.normalizer.object:
class: Symfony\Component\Serializer\Normalizer\ObjectNormalizer
arguments: ["#serializer.mapping.class_metadata_factory", null, "#serializer.property_accessor"]
public: false
tags: [serializer.normalizer]
calls:
- method: setCircularReferenceLimit
arguments: [1]
- method: setCircularReferenceHandler
arguments: ["#circular_reference_handler"]
The factory can be like this:
namespace AppBundle\Serializer;
class CircularHandlerFactory
{
/**
* #return \Closure
*/
public static function getId()
{
return function ($object) {
return $object->getId();
};
}
}
Another idea is to use the GROUPS for the serializer. There is an annotation given by FosRestBundle :
#Rest\View(serializerGroups={"user"})
More information here:
https://symfony.com/doc/current/serializer.html#using-serialization-groups-annotations
Symfony2, FOSRestBundle. How to use group with JMSSerializerBundle?

How to avoid duplicates on ManyToMany relation with Doctrine2 and Zend Framework2?

The objective is to have 2 entity Article and Tag, with a many to many relation where the tags in Tag Table remain unique even if declare the same tag for other article.
I try to explain better with code:
Article Entity:
/**
* #ORM\Entity
* #ORM\Table(name="articles")
*/
class Article {
/**
* #ORM\Id #ORM\GeneratedValue(strategy="AUTO") #ORM\Column(type="integer")
* #var int
*/
protected $id;
/**
* #ORM\Column(type="string")
* #var string
*/
protected $name;
/**
* #ORM\ManyToMany(targetEntity="Tag\Entity\Tag",inversedBy="platforms",cascade={"persist","remove"})
*
* #return Tag[]
*/
protected $tags;
public function __construct() {
$this->tags = new ArrayCollection();
}
public function setId($id) {
$this->id = $id;
}
public function getId() {
return $this->id;
}
public function setName($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
public function setTags($tags){
$this->tags = $tags;
}
public function setTagsFromArray(array $tags){
foreach($tags as $tag){
$this->tags->add(new Tag($tag));
}
}
/**
* Return the associated Tags
*
* #param boolean $getOnlyTagsName [optional] If set to true return a simple array of string (tags name).
* If set to false return array of Tag objects.
*
* #return Tag[]|string[]
*/
public function getTags($getOnlyTagsName=false) {
if ($getOnlyTagsName){
return array_map(function($i) { return $i->getName(); }, $this->tags->toArray());
}
return $this->tags;
}
public function addTags($tags) {
foreach($tags as $tag){
$this->tags->add($tag);
}
}
public function removeTags($tags) {
foreach ($tags as $tag){
$this->tags->removeElement($tag);
}
}
}
Tag Entity:
/**
* #ORM\Entity
* #ORM\Table(name="tags")
* ORM\HasLifecycleCallbacks // NOT USED
*/
class Tag {
/**
* #ORM\Id #ORM\Column(type="integer") #ORM\GeneratedValue
* #var int
*/
protected $id;
/**
* #ORM\Column(type="string",unique=true)
* #var string
*/
protected $name;
/**
* #ORM\Column(type="datetime",name="created_at")
* #var datetime
*/
protected $createdAt;
/**
* #ORM\Column(type="datetime",name="updated_at")
* #var datetime
*/
protected $updatedAt;
/**
* #ORM\ManyToMany(targetEntity="Article\Entity\Article", mappedBy="tags")
* var Tag[]
*/
protected $platforms;
/**
* Constructor
*
* #param string $name Tag's name
*/
public function __construct($name = null) {
$this->setName($name);
$this->setCreatedAt(new DateTime('now'));
$this->setUpdatedAt(new DateTime('now'));
}
/**
* Avoid duplicate entries.
*
* ORM\PrePersist // NOT USED
*/
public function onPrePersist(LifecycleEventArgs $args) {
}
/**
* Avoid duplicate entries.
*
* ORM\PreUpdate // NOT USED
*/
public function onPreUpdate(PreUpdateEventArgs $args) {
}
public function setId($id) {
$this->id = $id;
}
/**
* Returns tag's id
*
* #return integer
*/
public function getId() {
return $this->id;
}
/**
* Sets the tag's name
*
* #param string $name Name to set
*/
public function setName($name) {
$this->name = $name;
}
/**
* Returns tag's name
*
* #return string
*/
public function getName() {
return $this->name;
}
public function setCreatedAt(DateTime $date) {
$this->createdAt = $date;
}
public function getCreatedAt() {
return $this->createdAt;
}
public function setUpdatedAt(DateTime $date) {
$this->updatedAt = $date;
}
public function getUpdatedAt() {
return $this->updatedAt;
}
}
Then I have the Article Form (with Article Fieldset) where there is a tagsinput (jquery plugin) element. So the form post it's like:
object(Zend\Stdlib\Parameters)[151]
public 'security' => string 'dc2a6ff900fbc87933e07bd35ef36709...' (length=65)
public 'article' =>
array (size=3)
'id' => string '' (length=0)
'name' => string 'Article 1' (length=13)
'tags' =>
array (size=3)
0 => string 'tag1' (length=3)
1 => string 'tag2' (length=4)
2 => string 'tag3' (length=7)
public 'submit' => string 'Add' (length=8)
A the first insert all goes well, but when I try to insert another article with one of the article1 tags I get the error:
An exception occurred while executing 'INSERT INTO tags (name, created_at, updated_at) VALUES (?, ?, ?)' with params ["tag2", "2014-04-26 22:05:37", "2014-04-26 22:05:37"]:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'tag2' for key 'UNIQ_6FBC94265E237E06'
I know that I can use prePersist and preUpdate event listner, but I made some test but I don't know how to use Unit Of Work to avoid duplicates.
You can't have the Tag's name property (and database field) defined as unique. You'll have the Integrity constraint violation every time you set a new tag with a previously picked tag name. You just need to control that a name is not picked for a tag related to the same article.
You could add a Zend\Validator\Db\NoRecordExists validator to the tagsinput element to achieve that. It would be something like that:
$tagsinput->getValidatorChain()->addValidator(
new NoRecordExists( array(
'adapter' => $this->getServiceManager()->get( 'Zend\Db\Adapter\Adapter' ),
'table' => 'tag',
'field' => 'name',
'message' => 'The tag name already exists',
'exclude' => 'article_id = ' . $article->getId(),
) );
--
Edit:
This solution won't work if there are more than one tag, when tagsinput's value will be an array. You could iterate through the array to validate each tag, but it's not an efficient solution, it would execute N queries. You could implement a similar validator which accepts a where clause like TAG_NAME IN ( 'tag1', 'tag2', 'tag3' )
--
Note that this validator doesn't work with Doctrine2; table, field and exclude parameters have database values.
This should work when adding tags to an existing article. To validate that a new article doesn't have duplicated tags, you could do it client side or you could write your own validator.

Synfony 2 File Uploader with Doctrine 2

I want to make a simple file uploader with Symfony 2 and Doctrine 2.
I've follow this tutorial :
http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html
and this one :
http://leny-bernard.com/fr/afficher/article/creer-un-site-facilement-en-symfony2-partie-4
Here is my Entity class :
namespace Luna\KBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\File;
/**
* Luna\KBundle\Entity\Media
*
* #ORM\Entity
*/
class Media
{
/**
* #var integer $id
*/
private $id;
/**
* #var string $title
*/
private $title;
/**
* #var text $description
*/
private $description;
/**
* #var string $author
*/
private $author;
/**
* #var string $source
*/
private $source;
/**
* #Assert\File(maxSize="6000000")
*/
private $paths;
/**
* #var string $type
*/
private $type;
/**
* #var Luna\KBundle\Entity\object
*/
private $idobject;
/***********************************METHODS***********************************/
/**
* Set idobject
*
* #param Luna\KBundle\Entity\Object $idobject
*/
public function setIdobject(\Luna\KBundle\Entity\object $idobject)
{
$this->idObject = $idObject;
}
/**
* Get idObject
*
* #return Luna\KBundle\Entity\Object
*/
public function getIdObject()
{
return $this->idObject;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* #param string $title
*/
public function setTitle($title)
{
$this->title = $title;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set description
*
* #param text $description
*/
public function setDescription($description)
{
$this->description = $description;
}
/**
* Get description
*
* #return text
*/
public function getDescription()
{
return $this->description;
}
/**
* Set author
*
* #param string $author
*/
public function setAuthor($author)
{
$this->author = $author;
}
/**
* Get author
*
* #return string
*/
public function getAuthor()
{
return $this->author;
}
/**
* Set source
*
* #param string $source
*/
public function setSource($source)
{
$this->source = $source;
}
/**
* Get source
*
* #return string
*/
public function getSource()
{
return $this->source;
}
/**
* Set paths
*
* #param string $paths
*/
public function setPaths($paths)
{
$this->paths = $paths;
}
/**
* Get paths
*
* #return string
*/
public function getPaths()
{
return $this->paths;
}
/**
* Set type
*
* #param string $type
*/
public function setType($type)
{
$this->type = $type;
}
/**
* Get type
*
* #return string
*/
public function getType()
{
return $this->type;
}
public function getAbsolutePath()
{
return null === $this->paths ? null : $this->getUploadRootDir().'/'.$this->paths;
}
public function getWebPath()
{
return null === $this->paths ? null : $this->getUploadDir().'/'.$this->paths;
}
protected function getUploadRootDir()
{
// the absolute directory path where uploaded documents should be saved
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
protected function getUploadDir()
{
// get rid of the __DIR__ so it doesn't screw when displaying uploaded doc/image in the view.
return 'uploads/mediaobject';
}
}
The fact is that #Assert\File(maxSize="6000000") is not working : I don't have a file uploader but just a simple texte field ?!
How can I make this works correctly ?
Regards Guys :)
EDIT : Here my Form builder
namespace Luna\KBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class MediaInit extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('title')
->add('description')
->add('author')
->add('source')
->add('paths')
->add('type')
->add('idObject')
;
}
}
And Here my twig template :
{% extends '::layout.html.twig' %}
{####################################### MEDIA INIT###########################}
{% block content %}
<h1>Creer un Media</h1>
Entrez les informations de votre media ici
<form action="{{ path('media_init') }}" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<p>
<button type="submit">Creer</button>
</p>
</form>
{% endblock %}
When you want to upload a file you need to declare a path field & a virtual file field. So, your class needs to look like :
class Media
{
private $path;
/**
* #Assert\File(maxSize="6000000")
*/
private $file;
}
And your form:
class MediaInit extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('file');
}
}
It seems that $builder variable in MediaInit class is not initialized properly (with "Media" type).